[ECMA-402] Intl.RelativeTimeFormat missing in WebKit
authorross.kirsling@sony.com <ross.kirsling@sony.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 20 Apr 2020 05:15:35 +0000 (05:15 +0000)
committerross.kirsling@sony.com <ross.kirsling@sony.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 20 Apr 2020 05:15:35 +0000 (05:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=209770

Reviewed by Darin Adler.

JSTests:

* stress/intl-relativetimeformat.js: Added.

* test262/config.yaml:
Enable Intl.RelativeTimeFormat feature with flag.

* test262/expectations.yaml:
Mark known failures.
Test for locale validation is not specific to RelativeTimeFormat and should be investigated separately.
Tests for Polish appear to be wrong and should be corrected in test262.

Source/JavaScriptCore:

This patch implements the recent ECMA-402 feature Intl.RelativeTimeFormat.

RelativeTimeFormat has format / formatToParts functions like NumberFormat / DateTimeFormat
and is used to turn a number and unit into a formatted relative time string, e.g.:

  new Intl.RelativeTimeFormat('en').format(10, 'day')
  > 'in 10 days'

  new Intl.RelativeTimeFormat('en', { numeric: 'auto' }).format(0, 'day')
  > 'today'

Implementation of RelativeTimeFormat#formatToParts makes direct use of NumberFormat#formatToParts,
as the relative time string consists of at most one formatted number with optional literal text on either side.

This feature is runtime-guarded by the `useIntlRelativeTimeFormat` option.

* CMakeLists.txt:
* DerivedSources-input.xcfilelist:
* DerivedSources-output.xcfilelist:
* DerivedSources.make:
* JavaScriptCore.xcodeproj/project.pbxproj:
* Sources.txt:
* runtime/CommonIdentifiers.h:
* runtime/IntlRelativeTimeFormat.cpp: Added.
* runtime/IntlRelativeTimeFormat.h: Added.
* runtime/IntlRelativeTimeFormatConstructor.cpp: Added.
* runtime/IntlRelativeTimeFormatConstructor.h: Added.
* runtime/IntlRelativeTimeFormatPrototype.cpp: Added.
* runtime/IntlRelativeTimeFormatPrototype.h: Added.
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):
(JSC::JSGlobalObject::visitChildren):
* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::relativeTimeFormatStructure):
* runtime/OptionsList.h:
* runtime/VM.cpp:
(JSC::VM::VM):
* runtime/VM.h:
Add feature and runtime option.

* runtime/IntlDateTimeFormat.cpp:
(JSC::IntlDateTimeFormat::formatToParts):
* runtime/IntlPluralRules.cpp:
(JSC::IntlPluralRules::initializePluralRules):
(JSC::IntlPluralRules::resolvedOptions):
Make "type" a property name.

* runtime/IntlNumberFormat.cpp:
(JSC::IntlNumberFormat::initializeNumberFormat):
(JSC::IntlNumberFormat::resolvedOptions):
(JSC::IntlNumberFormat::formatToPartsInternal):
(JSC::IntlNumberFormat::formatToParts):
* runtime/IntlNumberFormat.h:
Factor out formatToPartsInternal so that RelativeTimeFormat can use it with its own UNumberFormat.
(This logic is too complicated to duplicate; it's because ICU won't split, e.g., "10,000" into parts for us.)

* runtime/IntlObject.cpp:
(JSC::IntlObject::IntlObject):
(JSC::IntlObject::create):
(JSC::IntlObject::finishCreation):
(JSC::intlAvailableLocales):
(JSC::intlCollatorAvailableLocales):
(JSC::isUnicodeLocaleIdentifierType):
(JSC::supportedLocales):
(JSC::intlDateTimeFormatAvailableLocales): Deleted.
(JSC::intlNumberFormatAvailableLocales): Deleted.
* runtime/IntlObject.h:
(JSC::intlDateTimeFormatAvailableLocales):
(JSC::intlNumberFormatAvailableLocales):
(JSC::intlPluralRulesAvailableLocales):
(JSC::intlRelativeTimeFormatAvailableLocales):
Perform three corrections for Intl classes:
  1. Collator should be the only class with unique "available locales".
     [unum|udat]_getAvailable exist but they've deferred to uloc_getAvailable for 20 years.
  2. isUnicodeLocaleIdentifierType isn't just `alphanum{3,8}` but rather `alphanum{3,8} (sep alphanum{3,8})*`.
     This is my own mistake from r239941.
  3. supportedLocalesOf entries should not be frozen.
     Changed in https://github.com/tc39/ecma402/pull/278.

* tools/JSDollarVM.cpp:
(JSC::functionICUVersion):
(JSC::JSDollarVM::finishCreation):
Add $vm.icuVersion so that we can add per-line skips to stress tests.

Tools:

* Scripts/run-jsc-stress-tests:
Add runIntlRelativeTimeFormatEnabled.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@260349 268f45cc-cd09-0410-ab3c-d52691b4dbfc

32 files changed:
JSTests/ChangeLog
JSTests/stress/intl-relativetimeformat.js [new file with mode: 0644]
JSTests/test262/config.yaml
JSTests/test262/expectations.yaml
Source/JavaScriptCore/CMakeLists.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/DerivedSources-input.xcfilelist
Source/JavaScriptCore/DerivedSources-output.xcfilelist
Source/JavaScriptCore/DerivedSources.make
Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
Source/JavaScriptCore/Sources.txt
Source/JavaScriptCore/runtime/CommonIdentifiers.h
Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp
Source/JavaScriptCore/runtime/IntlNumberFormat.cpp
Source/JavaScriptCore/runtime/IntlNumberFormat.h
Source/JavaScriptCore/runtime/IntlObject.cpp
Source/JavaScriptCore/runtime/IntlObject.h
Source/JavaScriptCore/runtime/IntlPluralRules.cpp
Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.h [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlRelativeTimeFormatConstructor.cpp [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlRelativeTimeFormatConstructor.h [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlRelativeTimeFormatPrototype.cpp [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlRelativeTimeFormatPrototype.h [new file with mode: 0644]
Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.h
Source/JavaScriptCore/runtime/OptionsList.h
Source/JavaScriptCore/runtime/VM.cpp
Source/JavaScriptCore/runtime/VM.h
Source/JavaScriptCore/tools/JSDollarVM.cpp
Tools/ChangeLog
Tools/Scripts/run-jsc-stress-tests

index 63ceb93..1264283 100644 (file)
@@ -1,3 +1,20 @@
+2020-04-19  Ross Kirsling  <ross.kirsling@sony.com>
+
+        [ECMA-402] Intl.RelativeTimeFormat missing in WebKit
+        https://bugs.webkit.org/show_bug.cgi?id=209770
+
+        Reviewed by Darin Adler.
+
+        * stress/intl-relativetimeformat.js: Added.
+
+        * test262/config.yaml:
+        Enable Intl.RelativeTimeFormat feature with flag.
+
+        * test262/expectations.yaml:
+        Mark known failures.
+        Test for locale validation is not specific to RelativeTimeFormat and should be investigated separately.
+        Tests for Polish appear to be wrong and should be corrected in test262.
+
 2020-04-18  Keith Miller  <keith_miller@apple.com>
 
         Unreviewed, mark test passing.
diff --git a/JSTests/stress/intl-relativetimeformat.js b/JSTests/stress/intl-relativetimeformat.js
new file mode 100644 (file)
index 0000000..6deaf41
--- /dev/null
@@ -0,0 +1,273 @@
+//@ runIntlRelativeTimeFormatEnabled
+
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error(`expected ${expected} but got ${actual}`);
+}
+
+const icuVersion = $vm.icuVersion();
+function shouldBeForICUVersion(minimumVersion, actual, expected) {
+    if (icuVersion < minimumVersion)
+        return;
+
+    if (actual !== expected)
+        throw new Error(`expected ${expected} but got ${actual}`);
+}
+
+function shouldNotThrow(func) {
+    func();
+}
+
+function shouldThrow(func, errorType) {
+    let error;
+    try {
+        func();
+    } catch (e) {
+        error = e;
+    }
+
+    if (!(error instanceof errorType))
+        throw new Error(`Expected ${errorType.name}!`);
+}
+
+shouldBe(Intl.RelativeTimeFormat instanceof Function, true);
+shouldBe(Intl.RelativeTimeFormat.length, 0);
+shouldBe(Object.getOwnPropertyDescriptor(Intl.RelativeTimeFormat, 'prototype').writable, false);
+shouldBe(Object.getOwnPropertyDescriptor(Intl.RelativeTimeFormat, 'prototype').enumerable, false);
+shouldBe(Object.getOwnPropertyDescriptor(Intl.RelativeTimeFormat, 'prototype').configurable, false);
+
+shouldThrow(() => Intl.RelativeTimeFormat(), TypeError);
+shouldThrow(() => Intl.RelativeTimeFormat.call({}), TypeError);
+
+shouldThrow(() => new Intl.RelativeTimeFormat('$'), RangeError);
+shouldThrow(() => new Intl.RelativeTimeFormat('en', null), TypeError);
+
+shouldBe(new Intl.RelativeTimeFormat() instanceof Intl.RelativeTimeFormat, true);
+
+{
+    class DerivedRelativeTimeFormat extends Intl.RelativeTimeFormat {}
+
+    const drtf = new DerivedRelativeTimeFormat();
+    shouldBe(drtf instanceof DerivedRelativeTimeFormat, true);
+    shouldBe(drtf instanceof Intl.RelativeTimeFormat, true);
+    shouldBe(drtf.format, Intl.RelativeTimeFormat.prototype.format);
+    shouldBe(drtf.formatToParts, Intl.RelativeTimeFormat.prototype.formatToParts);
+    shouldBe(Object.getPrototypeOf(drtf), DerivedRelativeTimeFormat.prototype);
+    shouldBe(Object.getPrototypeOf(DerivedRelativeTimeFormat.prototype), Intl.RelativeTimeFormat.prototype);
+}
+
+shouldBe(Intl.RelativeTimeFormat.supportedLocalesOf.length, 1);
+shouldBe(Intl.RelativeTimeFormat.supportedLocalesOf() instanceof Array, true);
+shouldBe(JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf.call(null, 'en')), '["en"]');
+shouldBe(JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf.call({}, 'en')), '["en"]');
+shouldBe(JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf.call(1, 'en')), '["en"]');
+shouldBe(JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf(9)), '[]');
+shouldBe(JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf('en')), '["en"]');
+shouldBe(JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf({ length: 4, 1: 'en', 0: 'es', 3: 'de' })), '["es","en","de"]');
+shouldBe(JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf(['en', 'pt', 'en', 'es'])), '["en","pt","es"]');
+shouldBe(JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf('En-laTn-us-variant2-variant1-1abc-U-ko-tRue-A-aa-aaa-x-RESERVED')), '["en-Latn-US-variant2-variant1-1abc-a-aa-aaa-u-ko-x-reserved"]');
+shouldBe(JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf('no-bok')), '["nb"]');
+shouldBe(JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf('x-some-thing')), '[]');
+
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf(Object.create(null, { length: { get() { throw new Error(); } } })), Error);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf(Object.create(null, { length: { value: 1 }, 0: { get() { throw new Error(); } } })), Error);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf([{ toString() { throw new Error(); } }]), Error);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf([5]), TypeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf(''), RangeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf('a'), RangeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf('abcdefghij'), RangeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf('#$'), RangeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf('en-@-abc'), RangeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf('en-u'), RangeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf('en-u-kn-true-u-ko-true'), RangeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf('en-x'), RangeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf('en-*'), RangeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf('en-'), RangeError);
+shouldThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf('en--US'), RangeError);
+
+const 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
+    'i-klingon', // grandfathered tag
+    'cmn-hans-cn-t-ca-u-ca-x-t-u', // singleton subtags can also be used as private use subtags
+    'enochian-enochian', // language and variant subtags may be the same
+    '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
+    'x-en-US-12345', // anything goes in private use tags
+    'x-12345-12345-en-US',
+    'x-en-US-12345-12345',
+    'x-en-u-foo',
+    'x-en-u-foo-u-bar'
+];
+for (let validLanguageTag of validLanguageTags)
+    shouldNotThrow(() => Intl.RelativeTimeFormat.supportedLocalesOf(validLanguageTag));
+
+shouldBe(Object.getPrototypeOf(Intl.RelativeTimeFormat.prototype), Object.prototype);
+
+shouldBe(Intl.RelativeTimeFormat.prototype.constructor, Intl.RelativeTimeFormat);
+
+shouldBe(Intl.RelativeTimeFormat.prototype[Symbol.toStringTag], 'Intl.RelativeTimeFormat');
+shouldBe(Object.prototype.toString.call(Intl.RelativeTimeFormat.prototype), '[object Intl.RelativeTimeFormat]');
+shouldBe(Object.getOwnPropertyDescriptor(Intl.RelativeTimeFormat.prototype, Symbol.toStringTag).writable, false);
+shouldBe(Object.getOwnPropertyDescriptor(Intl.RelativeTimeFormat.prototype, Symbol.toStringTag).enumerable, false);
+shouldBe(Object.getOwnPropertyDescriptor(Intl.RelativeTimeFormat.prototype, Symbol.toStringTag).configurable, true);
+
+shouldThrow(() => new Intl.RelativeTimeFormat('en', { localeMatcher: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.RelativeTimeFormat('en', { localeMatcher:'bad' }), RangeError);
+shouldNotThrow(() => new Intl.RelativeTimeFormat('en', { localeMatcher:'lookup' }));
+shouldNotThrow(() => new Intl.RelativeTimeFormat('en', { localeMatcher:'best fit' }));
+
+const defaultRTF = new Intl.RelativeTimeFormat('en');
+
+shouldBe(Intl.RelativeTimeFormat.prototype.resolvedOptions.length, 0);
+shouldThrow(() => Intl.RelativeTimeFormat.prototype.resolvedOptions.call(5), TypeError);
+shouldThrow(() => Intl.RelativeTimeFormat.prototype.resolvedOptions.call({}), TypeError);
+shouldBe(defaultRTF.resolvedOptions() instanceof Object, true);
+shouldBe(defaultRTF.resolvedOptions() !== defaultRTF.resolvedOptions(), true);
+shouldBe(defaultRTF.resolvedOptions().locale, 'en');
+shouldBe(defaultRTF.resolvedOptions().style, 'long');
+shouldBe(defaultRTF.resolvedOptions().numeric, 'always');
+shouldBe(defaultRTF.resolvedOptions().numberingSystem, 'latn');
+
+shouldBe(new Intl.RelativeTimeFormat('en-u-nu-hanidec').resolvedOptions().locale, 'en-u-nu-hanidec');
+shouldBe(new Intl.RelativeTimeFormat('en-u-nu-hanidec', { numberingSystem: 'gujr' }).resolvedOptions().locale, 'en');
+shouldBe(new Intl.RelativeTimeFormat('en', { numberingSystem: 'hanidec' }).resolvedOptions().locale, 'en');
+shouldBe(new Intl.RelativeTimeFormat('en-u-ca-chinese').resolvedOptions().locale, 'en');
+
+shouldThrow(() => new Intl.RelativeTimeFormat('en', { style: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.RelativeTimeFormat('en', { style: 'bad' }), RangeError);
+shouldBe(new Intl.RelativeTimeFormat('en', { style: 'long' }).resolvedOptions().style, 'long');
+shouldBe(new Intl.RelativeTimeFormat('en', { style: 'short' }).resolvedOptions().style, 'short');
+shouldBe(new Intl.RelativeTimeFormat('en', { style: 'narrow' }).resolvedOptions().style, 'narrow');
+
+shouldThrow(() => new Intl.RelativeTimeFormat('en', { numeric: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.RelativeTimeFormat('en', { numeric: 'bad' }), RangeError);
+shouldBe(new Intl.RelativeTimeFormat('en', { numeric: 'always' }).resolvedOptions().numeric, 'always');
+shouldBe(new Intl.RelativeTimeFormat('en', { numeric: 'auto' }).resolvedOptions().numeric, 'auto');
+
+const numberingSystems = [
+    'arab', 'arabext', 'bali', 'beng', 'deva', 'fullwide', 'gujr', 'guru',
+    'hanidec', 'khmr', 'knda', 'laoo', 'latn', 'limb', 'mlym', 'mong', 'mymr',
+    'orya', 'tamldec', 'telu', 'thai', 'tibt'
+]
+for (let numberingSystem of numberingSystems) {
+    shouldBe(new Intl.RelativeTimeFormat('en', { numberingSystem }).resolvedOptions().numberingSystem, numberingSystem);
+    shouldBe(new Intl.RelativeTimeFormat(`en-u-nu-${numberingSystem}`).resolvedOptions().numberingSystem, numberingSystem);
+}
+
+shouldBe(Intl.RelativeTimeFormat.prototype.format.length, 2);
+shouldThrow(() => Intl.RelativeTimeFormat.prototype.format.call({}, 3, 'days'), TypeError);
+shouldThrow(() => defaultRTF.format(Symbol(), 'days'), TypeError);
+shouldThrow(() => defaultRTF.format(3, Symbol()), TypeError);
+shouldThrow(() => defaultRTF.format(Infinity, 'days'), RangeError);
+shouldThrow(() => defaultRTF.format(3, 'centuries'), RangeError);
+
+const units = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
+if (icuVersion >= 63)
+    units.push('quarter');
+
+for (let unit of units) {
+    shouldBe(defaultRTF.format(10, unit), defaultRTF.format(10, `${unit}s`));
+
+    shouldBe(defaultRTF.format(10000.5, unit), `in 10,000.5 ${unit}s`);
+    shouldBe(defaultRTF.format(10, unit), `in 10 ${unit}s`);
+    shouldBe(defaultRTF.format(0, unit), `in 0 ${unit}s`);
+    shouldBe(defaultRTF.format(-10, unit), `10 ${unit}s ago`);
+    shouldBe(defaultRTF.format(-10000.5, unit), `10,000.5 ${unit}s ago`);
+
+    shouldBeForICUVersion(64, defaultRTF.format(1, unit), `in 1 ${unit}`);
+    shouldBeForICUVersion(63, defaultRTF.format(-0, unit), `0 ${unit}s ago`);
+    shouldBeForICUVersion(64, defaultRTF.format(-1, unit), `1 ${unit} ago`);
+}
+
+shouldBe(new Intl.RelativeTimeFormat('en', { style: 'short' }).format(10, 'second'), 'in 10 sec.');
+shouldBe(new Intl.RelativeTimeFormat('ru', { style: 'short' }).format(10, 'second'), 'через 10 сек.');
+shouldBe(new Intl.RelativeTimeFormat('en', { style: 'narrow' }).format(10, 'second'), 'in 10 sec.');
+shouldBe(new Intl.RelativeTimeFormat('ru', { style: 'narrow' }).format(10, 'second'), '+10 с');
+
+shouldBe(new Intl.RelativeTimeFormat('en', { numeric: 'auto' }).format(0, 'second'), 'now');
+shouldBe(new Intl.RelativeTimeFormat('ja', { numeric: 'auto' }).format(0, 'second'), '今');
+shouldBe(new Intl.RelativeTimeFormat('en', { numeric: 'auto' }).format(0, 'day'), 'today');
+shouldBe(new Intl.RelativeTimeFormat('ja', { numeric: 'auto' }).format(0, 'day'), '今日');
+shouldBe(new Intl.RelativeTimeFormat('en', { numeric: 'auto' }).format(0, 'year'), 'this year');
+shouldBe(new Intl.RelativeTimeFormat('ja', { numeric: 'auto' }).format(0, 'year'), '今年');
+
+shouldBe(new Intl.RelativeTimeFormat('en', { numberingSystem: 'thai' }).format(-10, 'hour'), '๑๐ hours ago');
+shouldBe(new Intl.RelativeTimeFormat('en-u-nu-thai').format(-10, 'hour'), '๑๐ hours ago');
+shouldBe(new Intl.RelativeTimeFormat('ko', { numberingSystem: 'hanidec' }).format(-10, 'hour'), '一〇시간 전');
+shouldBe(new Intl.RelativeTimeFormat('ko-u-nu-hanidec').format(-10, 'hour'), '一〇시간 전');
+
+shouldBe(Intl.RelativeTimeFormat.prototype.formatToParts.length, 2);
+shouldThrow(() => Intl.RelativeTimeFormat.prototype.formatToParts.call({}, 3, 'days'), TypeError);
+shouldThrow(() => defaultRTF.formatToParts(Symbol(), 'days'), TypeError);
+shouldThrow(() => defaultRTF.formatToParts(3, Symbol()), TypeError);
+shouldThrow(() => defaultRTF.formatToParts(Infinity, 'days'), RangeError);
+shouldThrow(() => defaultRTF.formatToParts(3, 'centuries'), RangeError);
+
+for (let unit of units) {
+    shouldBe(JSON.stringify(defaultRTF.formatToParts(10, unit)), JSON.stringify(defaultRTF.formatToParts(10, `${unit}s`)));
+
+    const concatenateValues = (parts) => parts.map(part => part.value).join('');
+
+    shouldBe(concatenateValues(defaultRTF.formatToParts(10000.5, unit)), `in 10,000.5 ${unit}s`);
+    shouldBe(concatenateValues(defaultRTF.formatToParts(10, unit)), `in 10 ${unit}s`);
+    shouldBe(concatenateValues(defaultRTF.formatToParts(0, unit)), `in 0 ${unit}s`);
+    shouldBe(concatenateValues(defaultRTF.formatToParts(-10, unit)), `10 ${unit}s ago`);
+    shouldBe(concatenateValues(defaultRTF.formatToParts(-10000.5, unit)), `10,000.5 ${unit}s ago`);
+
+    shouldBeForICUVersion(64, concatenateValues(defaultRTF.formatToParts(1, unit)), `in 1 ${unit}`);
+    shouldBeForICUVersion(63, concatenateValues(defaultRTF.formatToParts(-0, unit)), `0 ${unit}s ago`);
+    shouldBeForICUVersion(64, concatenateValues(defaultRTF.formatToParts(-1, unit)), `1 ${unit} ago`);
+}
+
+shouldBe(
+    JSON.stringify(defaultRTF.formatToParts(10000.5, 'day')),
+    JSON.stringify([
+        { type: 'literal', value: 'in ' },
+        { type: 'integer', value: '10', unit: 'day' },
+        { type: 'group', value: ',', unit: 'day' },
+        { type: 'integer', value: '000', unit: 'day' },
+        { type: 'decimal', value: '.', unit: 'day' },
+        { type: 'fraction', value: '5', unit: 'day' },
+        { type: 'literal', value: ' days' }
+    ])
+);
+
+shouldBe(
+    JSON.stringify(new Intl.RelativeTimeFormat('sw').formatToParts(10, 'year')),
+    JSON.stringify([
+        { type: 'literal', value: 'baada ya miaka ' },
+        { type: 'integer', value: '10', unit: 'year' },
+    ])
+);
+
+shouldBe(
+    JSON.stringify(new Intl.RelativeTimeFormat('ru', { style: 'narrow' }).formatToParts(10, 'second')),
+    JSON.stringify([
+        { type: 'literal', value: '+' },
+        { type: 'integer', value: '10', unit: 'second' },
+        { type: 'literal', value: ' с' }
+    ])
+);
+
+shouldBe(
+    JSON.stringify(new Intl.RelativeTimeFormat('ja', { numeric: 'auto' }).formatToParts(0, 'week')),
+    JSON.stringify([
+        { type: 'literal', value: '今週' }
+    ])
+);
+
+shouldBe(
+    JSON.stringify(new Intl.RelativeTimeFormat('ko', { numberingSystem: 'hanidec' }).formatToParts(-10, 'hour')),
+    JSON.stringify([
+        { type: 'integer', value: '一〇', unit: 'hour' },
+        { type: 'literal', value: '시간 전' }
+    ])
+);
index a0366ce..8c55324 100644 (file)
@@ -2,6 +2,7 @@
 ---
 flags:
   BigInt: useBigInt
+  Intl.RelativeTimeFormat: useIntlRelativeTimeFormat
   WeakRef: useWeakRefs
   class-fields-public: usePublicClassFields
   logical-assignment-operators: useLogicalAssignmentOperators
@@ -30,7 +31,6 @@ skip:
     - Intl.ListFormat
     - Intl.Locale
     - Intl.NumberFormat-unified
-    - Intl.RelativeTimeFormat
     - Intl.Segmenter
   paths:
     - test/built-ins/DataView/prototype/getBigInt64
@@ -153,3 +153,7 @@ skip:
     # https://bugs.webkit.org/show_bug.cgi?id=209783
     - test/intl402/DateTimeFormat/prototype/format/related-year-zh.js
     - test/intl402/DateTimeFormat/prototype/formatToParts/related-year-zh.js
+
+    # requires ICU 65 (https://unicode-org.atlassian.net/browse/ICU-20654)
+    - test/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-auto.js
+    - test/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-auto.js
index 25c66f0..8988e37 100644 (file)
@@ -1935,12 +1935,33 @@ test/intl402/NumberFormat/proto-from-ctor-realm.js:
 test/intl402/PluralRules/proto-from-ctor-realm.js:
   default: 'Test262Error: newTarget.prototype is undefined Expected SameValue(«[object Object]», «[object Object]») to be true'
   strict mode: 'Test262Error: newTarget.prototype is undefined Expected SameValue(«[object Object]», «[object Object]») to be true'
+test/intl402/RelativeTimeFormat/constructor/constructor/locales-valid.js:
+  default: 'Test262Error: Grandfathered Expected a RangeError to be thrown but no exception was thrown at all'
+  strict mode: 'Test262Error: Grandfathered Expected a RangeError to be thrown but no exception was thrown at all'
+test/intl402/RelativeTimeFormat/constructor/constructor/proto-from-ctor-realm.js:
+  default: 'Test262Error: newTarget.prototype is undefined Expected SameValue(«[object Intl.RelativeTimeFormat]», «[object Intl.RelativeTimeFormat]») to be true'
+  strict mode: 'Test262Error: newTarget.prototype is undefined Expected SameValue(«[object Intl.RelativeTimeFormat]», «[object Intl.RelativeTimeFormat]») to be true'
+test/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-long.js:
+  default: 'Test262Error: Expected SameValue(«za 1000 sekund», «za 1 000 sekund») to be true'
+  strict mode: 'Test262Error: Expected SameValue(«za 1000 sekund», «za 1 000 sekund») to be true'
+test/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-narrow.js:
+  default: 'Test262Error: Expected SameValue(«za 1000 s», «za 1 000 s») to be true'
+  strict mode: 'Test262Error: Expected SameValue(«za 1000 s», «za 1 000 s») to be true'
+test/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-short.js:
+  default: 'Test262Error: Expected SameValue(«za 1000 sek.», «za 1 000 sek.») to be true'
+  strict mode: 'Test262Error: Expected SameValue(«za 1000 sek.», «za 1 000 sek.») to be true'
+test/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-long.js:
+  default: 'Test262Error: formatToParts(1000, second): length Expected SameValue(«3», «5») to be true'
+  strict mode: 'Test262Error: formatToParts(1000, second): length Expected SameValue(«3», «5») to be true'
+test/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-narrow.js:
+  default: 'Test262Error: formatToParts(1000, second): length Expected SameValue(«3», «5») to be true'
+  strict mode: 'Test262Error: formatToParts(1000, second): length Expected SameValue(«3», «5») to be true'
+test/intl402/RelativeTimeFormat/prototype/formatToParts/pl-pl-style-short.js:
+  default: 'Test262Error: formatToParts(1000, second): length Expected SameValue(«3», «5») to be true'
+  strict mode: 'Test262Error: formatToParts(1000, second): length Expected SameValue(«3», «5») to be true'
 test/intl402/language-tags-invalid.js:
   default: 'Test262Error: Test data "de-gregory-gregory" is a canonicalized and structurally valid language tag.'
   strict mode: 'Test262Error: Test data "de-gregory-gregory" is a canonicalized and structurally valid language tag.'
-test/intl402/supportedLocalesOf-returned-array-elements-are-not-frozen.js:
-  default: 'Test262Error: Property 0 of object returned by SupportedLocales is not writable. Expected SameValue(«false», «true») to be true (Testing with Collator.)'
-  strict mode: 'Test262Error: Property 0 of object returned by SupportedLocales is not writable. Expected SameValue(«false», «true») to be true (Testing with Collator.)'
 test/language/arguments-object/mapped/nonconfigurable-nonenumerable-nonwritable-descriptors-set-by-arguments.js:
   default: 'Test262Error: Expected obj[0] to have enumerable:false.'
 test/language/arguments-object/mapped/nonconfigurable-nonenumerable-nonwritable-descriptors-set-by-param.js:
index d1d9232..afb2547 100644 (file)
@@ -75,6 +75,8 @@ set(JavaScriptCore_OBJECT_LUT_SOURCES
     runtime/IntlObject.cpp
     runtime/IntlPluralRulesConstructor.cpp
     runtime/IntlPluralRulesPrototype.cpp
+    runtime/IntlRelativeTimeFormatConstructor.cpp
+    runtime/IntlRelativeTimeFormatPrototype.cpp
     runtime/JSDataViewPrototype.cpp
     runtime/JSGlobalObject.cpp
     runtime/JSInternalPromiseConstructor.cpp
index a38430a..d7682fc 100644 (file)
@@ -1,3 +1,94 @@
+2020-04-19  Ross Kirsling  <ross.kirsling@sony.com>
+
+        [ECMA-402] Intl.RelativeTimeFormat missing in WebKit
+        https://bugs.webkit.org/show_bug.cgi?id=209770
+
+        Reviewed by Darin Adler.
+
+        This patch implements the recent ECMA-402 feature Intl.RelativeTimeFormat.
+
+        RelativeTimeFormat has format / formatToParts functions like NumberFormat / DateTimeFormat
+        and is used to turn a number and unit into a formatted relative time string, e.g.:
+
+          new Intl.RelativeTimeFormat('en').format(10, 'day')
+          > 'in 10 days'
+
+          new Intl.RelativeTimeFormat('en', { numeric: 'auto' }).format(0, 'day')
+          > 'today'
+
+        Implementation of RelativeTimeFormat#formatToParts makes direct use of NumberFormat#formatToParts,
+        as the relative time string consists of at most one formatted number with optional literal text on either side.
+
+        This feature is runtime-guarded by the `useIntlRelativeTimeFormat` option.
+
+        * CMakeLists.txt:
+        * DerivedSources-input.xcfilelist:
+        * DerivedSources-output.xcfilelist:
+        * DerivedSources.make:
+        * JavaScriptCore.xcodeproj/project.pbxproj:
+        * Sources.txt:
+        * runtime/CommonIdentifiers.h:
+        * runtime/IntlRelativeTimeFormat.cpp: Added.
+        * runtime/IntlRelativeTimeFormat.h: Added.
+        * runtime/IntlRelativeTimeFormatConstructor.cpp: Added.
+        * runtime/IntlRelativeTimeFormatConstructor.h: Added.
+        * runtime/IntlRelativeTimeFormatPrototype.cpp: Added.
+        * runtime/IntlRelativeTimeFormatPrototype.h: Added.
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::init):
+        (JSC::JSGlobalObject::visitChildren):
+        * runtime/JSGlobalObject.h:
+        (JSC::JSGlobalObject::relativeTimeFormatStructure):
+        * runtime/OptionsList.h:
+        * runtime/VM.cpp:
+        (JSC::VM::VM):
+        * runtime/VM.h:
+        Add feature and runtime option.
+
+        * runtime/IntlDateTimeFormat.cpp:
+        (JSC::IntlDateTimeFormat::formatToParts):
+        * runtime/IntlPluralRules.cpp:
+        (JSC::IntlPluralRules::initializePluralRules):
+        (JSC::IntlPluralRules::resolvedOptions):
+        Make "type" a property name.
+
+        * runtime/IntlNumberFormat.cpp:
+        (JSC::IntlNumberFormat::initializeNumberFormat):
+        (JSC::IntlNumberFormat::resolvedOptions):
+        (JSC::IntlNumberFormat::formatToPartsInternal):
+        (JSC::IntlNumberFormat::formatToParts):
+        * runtime/IntlNumberFormat.h:
+        Factor out formatToPartsInternal so that RelativeTimeFormat can use it with its own UNumberFormat.
+        (This logic is too complicated to duplicate; it's because ICU won't split, e.g., "10,000" into parts for us.)
+
+        * runtime/IntlObject.cpp:
+        (JSC::IntlObject::IntlObject):
+        (JSC::IntlObject::create):
+        (JSC::IntlObject::finishCreation):
+        (JSC::intlAvailableLocales):
+        (JSC::intlCollatorAvailableLocales):
+        (JSC::isUnicodeLocaleIdentifierType):
+        (JSC::supportedLocales):
+        (JSC::intlDateTimeFormatAvailableLocales): Deleted.
+        (JSC::intlNumberFormatAvailableLocales): Deleted.
+        * runtime/IntlObject.h:
+        (JSC::intlDateTimeFormatAvailableLocales):
+        (JSC::intlNumberFormatAvailableLocales):
+        (JSC::intlPluralRulesAvailableLocales):
+        (JSC::intlRelativeTimeFormatAvailableLocales):
+        Perform three corrections for Intl classes:
+          1. Collator should be the only class with unique "available locales".
+             [unum|udat]_getAvailable exist but they've deferred to uloc_getAvailable for 20 years.
+          2. isUnicodeLocaleIdentifierType isn't just `alphanum{3,8}` but rather `alphanum{3,8} (sep alphanum{3,8})*`.
+             This is my own mistake from r239941.
+          3. supportedLocalesOf entries should not be frozen.
+             Changed in https://github.com/tc39/ecma402/pull/278.
+
+        * tools/JSDollarVM.cpp:
+        (JSC::functionICUVersion):
+        (JSC::JSDollarVM::finishCreation):
+        Add $vm.icuVersion so that we can add per-line skips to stress tests.
+
 2020-04-19  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] SlowPathCall is not supported by callOperation in Windows
index 2340cdf..34acdf8 100644 (file)
@@ -142,6 +142,8 @@ $(PROJECT_DIR)/runtime/IntlNumberFormatPrototype.cpp
 $(PROJECT_DIR)/runtime/IntlObject.cpp
 $(PROJECT_DIR)/runtime/IntlPluralRulesConstructor.cpp
 $(PROJECT_DIR)/runtime/IntlPluralRulesPrototype.cpp
+$(PROJECT_DIR)/runtime/IntlRelativeTimeFormatConstructor.cpp
+$(PROJECT_DIR)/runtime/IntlRelativeTimeFormatPrototype.cpp
 $(PROJECT_DIR)/runtime/JSDataViewPrototype.cpp
 $(PROJECT_DIR)/runtime/JSGlobalObject.cpp
 $(PROJECT_DIR)/runtime/JSInternalPromiseConstructor.cpp
index a74450c..daf9432 100644 (file)
@@ -33,6 +33,8 @@ $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlNumberFormatPrototype.lu
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlObject.lut.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlPluralRulesConstructor.lut.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlPluralRulesPrototype.lut.h
+$(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlRelativeTimeFormatConstructor.lut.h
+$(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlRelativeTimeFormatPrototype.lut.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/JSCBuiltins.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/JSDataViewPrototype.lut.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/JSGlobalObject.lut.h
index e9d804d..e7f2131 100644 (file)
@@ -153,6 +153,8 @@ OBJECT_LUT_HEADERS = \
     IntlObject.lut.h \
     IntlPluralRulesConstructor.lut.h \
     IntlPluralRulesPrototype.lut.h \
+    IntlRelativeTimeFormatConstructor.lut.h \
+    IntlRelativeTimeFormatPrototype.lut.h \
     JSDataViewPrototype.lut.h \
     JSGlobalObject.lut.h \
     JSInternalPromiseConstructor.lut.h \
index 70fc99f..2046b08 100644 (file)
                A1E0451B1C25B4B100BB663C /* StringPrototype.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = StringPrototype.js; sourceTree = "<group>"; };
                A1FE1EB01C2C537E00A289FF /* DatePrototype.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = DatePrototype.js; sourceTree = "<group>"; };
                A27958D7FA1142B0AC9E364D /* WasmContextInlines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WasmContextInlines.h; sourceTree = "<group>"; };
+               A3BF884B24480BDE001B9F35 /* IntlRelativeTimeFormatPrototype.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntlRelativeTimeFormatPrototype.cpp; sourceTree = "<group>"; };
+               A3BF884C24480BDF001B9F35 /* IntlRelativeTimeFormatPrototype.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntlRelativeTimeFormatPrototype.h; sourceTree = "<group>"; };
+               A3BF884D24480BDF001B9F35 /* IntlRelativeTimeFormatConstructor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntlRelativeTimeFormatConstructor.cpp; sourceTree = "<group>"; };
+               A3BF884E24480BE0001B9F35 /* IntlRelativeTimeFormatConstructor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntlRelativeTimeFormatConstructor.h; sourceTree = "<group>"; };
+               A3BF884F24480BE0001B9F35 /* IntlRelativeTimeFormat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntlRelativeTimeFormat.cpp; sourceTree = "<group>"; };
+               A3BF885024480BE1001B9F35 /* IntlRelativeTimeFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntlRelativeTimeFormat.h; sourceTree = "<group>"; };
                A3FF9BC52234746600B1A9AB /* YarrFlags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = YarrFlags.h; path = yarr/YarrFlags.h; sourceTree = "<group>"; };
                A3FF9BC62234746600B1A9AB /* YarrFlags.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = YarrFlags.cpp; path = yarr/YarrFlags.cpp; sourceTree = "<group>"; };
                A503FA13188E0FAF00110F14 /* JavaScriptCallFrame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JavaScriptCallFrame.cpp; sourceTree = "<group>"; };
                                7D5FB19020744BF1005DDF64 /* IntlPluralRulesConstructor.h */,
                                7D5FB19120744BF1005DDF64 /* IntlPluralRulesPrototype.cpp */,
                                7D5FB18D20744AE8005DDF64 /* IntlPluralRulesPrototype.h */,
+                               A3BF884F24480BE0001B9F35 /* IntlRelativeTimeFormat.cpp */,
+                               A3BF885024480BE1001B9F35 /* IntlRelativeTimeFormat.h */,
+                               A3BF884D24480BDF001B9F35 /* IntlRelativeTimeFormatConstructor.cpp */,
+                               A3BF884E24480BE0001B9F35 /* IntlRelativeTimeFormatConstructor.h */,
+                               A3BF884B24480BDE001B9F35 /* IntlRelativeTimeFormatPrototype.cpp */,
+                               A3BF884C24480BDF001B9F35 /* IntlRelativeTimeFormatPrototype.h */,
                                0F275F2C1ECE079600620D47 /* Intrinsic.cpp */,
                                86BF642A148DB2B5004DE36A /* Intrinsic.h */,
                                8B9F6D551D5912FA001C739F /* IterationKind.h */,
index b3692f5..0f4c50d 100644 (file)
@@ -819,6 +819,9 @@ runtime/IntlObject.cpp
 runtime/IntlPluralRules.cpp
 runtime/IntlPluralRulesConstructor.cpp
 runtime/IntlPluralRulesPrototype.cpp
+runtime/IntlRelativeTimeFormat.cpp
+runtime/IntlRelativeTimeFormatConstructor.cpp
+runtime/IntlRelativeTimeFormatPrototype.cpp
 runtime/IteratorOperations.cpp
 runtime/IteratorPrototype.cpp
 runtime/JSArray.cpp
index e2dc887..d164ba4 100644 (file)
@@ -49,6 +49,7 @@
     macro(Promise) \
     macro(Reflect) \
     macro(RegExp) \
+    macro(RelativeTimeFormat) \
     macro(RemotePlayback) \
     macro(Set) \
     macro(SharedArrayBuffer) \
     macro(stack) \
     macro(stackTraceLimit) \
     macro(sticky) \
+    macro(style) \
     macro(subarray) \
     macro(summary) \
     macro(target) \
     macro(toLocaleString) \
     macro(toPrecision) \
     macro(toString) \
+    macro(type) \
     macro(uid) \
     macro(unicode) \
     macro(usage) \
index 00302fe..2d133d6 100644 (file)
@@ -1029,7 +1029,6 @@ JSValue IntlDateTimeFormat::formatToParts(JSGlobalObject* globalObject, double v
         return throwOutOfMemoryError(globalObject, scope);
 
     auto resultString = String(result.data(), resultLength);
-    auto typePropertyName = Identifier::fromString(vm, "type");
     auto literalString = jsNontrivialString(vm, "literal"_s);
 
     int32_t previousEndIndex = 0;
@@ -1043,7 +1042,7 @@ JSValue IntlDateTimeFormat::formatToParts(JSGlobalObject* globalObject, double v
         if (previousEndIndex < beginIndex) {
             auto value = jsString(vm, resultString.substring(previousEndIndex, beginIndex - previousEndIndex));
             JSObject* part = constructEmptyObject(globalObject);
-            part->putDirect(vm, typePropertyName, literalString);
+            part->putDirect(vm, vm.propertyNames->type, literalString);
             part->putDirect(vm, vm.propertyNames->value, value);
             parts->push(globalObject, part);
             RETURN_IF_EXCEPTION(scope, { });
@@ -1054,7 +1053,7 @@ JSValue IntlDateTimeFormat::formatToParts(JSGlobalObject* globalObject, double v
             auto type = jsString(vm, partTypeString(UDateFormatField(fieldType)));
             auto value = jsString(vm, resultString.substring(beginIndex, endIndex - beginIndex));
             JSObject* part = constructEmptyObject(globalObject);
-            part->putDirect(vm, typePropertyName, type);
+            part->putDirect(vm, vm.propertyNames->type, type);
             part->putDirect(vm, vm.propertyNames->value, value);
             parts->push(globalObject, part);
             RETURN_IF_EXCEPTION(scope, { });
index ca42796..0621f3d 100644 (file)
@@ -2,6 +2,7 @@
  * Copyright (C) 2015 Andy VanWagoner (andy@vanwagoner.family)
  * Copyright (C) 2016 Sukolsak Sakshuwong (sukolsak@gmail.com)
  * Copyright (C) 2016-2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -195,7 +196,7 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa
 
     m_numberingSystem = result.get("nu"_s);
 
-    String styleString = intlStringOption(globalObject, options, Identifier::fromString(vm, "style"), { "decimal", "percent", "currency" }, "style must be either \"decimal\", \"percent\", or \"currency\"", "decimal");
+    String styleString = intlStringOption(globalObject, options, vm.propertyNames->style, { "decimal", "percent", "currency" }, "style must be either \"decimal\", \"percent\", or \"currency\"", "decimal");
     RETURN_IF_EXCEPTION(scope, void());
     if (styleString == "decimal")
         m_style = Style::Decimal;
@@ -441,7 +442,7 @@ JSObject* IntlNumberFormat::resolvedOptions(JSGlobalObject* globalObject)
     JSObject* options = constructEmptyObject(globalObject);
     options->putDirect(vm, vm.propertyNames->locale, jsString(vm, m_locale));
     options->putDirect(vm, vm.propertyNames->numberingSystem, jsString(vm, m_numberingSystem));
-    options->putDirect(vm, Identifier::fromString(vm, "style"), jsNontrivialString(vm, styleString(m_style)));
+    options->putDirect(vm, vm.propertyNames->style, jsNontrivialString(vm, styleString(m_style)));
     if (m_style == Style::Currency) {
         options->putDirect(vm, Identifier::fromString(vm, "currency"), jsNontrivialString(vm, m_currency));
         options->putDirect(vm, Identifier::fromString(vm, "currencyDisplay"), jsNontrivialString(vm, currencyDisplayString(m_currencyDisplay)));
@@ -503,6 +504,52 @@ ASCIILiteral IntlNumberFormat::partTypeString(UNumberFormatFields field, double
     return "unknown"_s;
 }
 
+void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, double value, const String& formatted, UFieldPositionIterator* iterator, JSArray* parts, JSString* unit)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto stringLength = formatted.length();
+
+    int32_t literalFieldType = -1;
+    auto literalField = IntlNumberFormatField(literalFieldType, stringLength);
+    Vector<IntlNumberFormatField, 32> fields(stringLength, literalField);
+    int32_t beginIndex = 0;
+    int32_t endIndex = 0;
+    auto fieldType = ufieldpositer_next(iterator, &beginIndex, &endIndex);
+    while (fieldType >= 0) {
+        auto size = endIndex - beginIndex;
+        for (auto i = beginIndex; i < endIndex; ++i) {
+            // Only override previous value if new value is more specific.
+            if (fields[i].size >= size)
+                fields[i] = IntlNumberFormatField(fieldType, size);
+        }
+        fieldType = ufieldpositer_next(iterator, &beginIndex, &endIndex);
+    }
+
+    auto literalString = jsNontrivialString(vm, "literal"_s);
+    Identifier unitName;
+    if (unit)
+        unitName = Identifier::fromString(vm, "unit");
+
+    size_t currentIndex = 0;
+    while (currentIndex < stringLength) {
+        auto startIndex = currentIndex;
+        auto fieldType = fields[currentIndex].type;
+        while (currentIndex < stringLength && fields[currentIndex].type == fieldType)
+            ++currentIndex;
+        auto partType = fieldType == literalFieldType ? literalString : jsString(vm, partTypeString(UNumberFormatFields(fieldType), value));
+        auto partValue = jsSubstring(vm, formatted, startIndex, currentIndex - startIndex);
+        JSObject* part = constructEmptyObject(globalObject);
+        part->putDirect(vm, vm.propertyNames->type, partType);
+        part->putDirect(vm, vm.propertyNames->value, partValue);
+        if (unit)
+            part->putDirect(vm, unitName, unit);
+        parts->push(globalObject, part);
+        RETURN_IF_EXCEPTION(scope, void());
+    }
+}
+
 JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double value)
 {
     VM& vm = globalObject->vm();
@@ -520,7 +567,6 @@ JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double val
     if (U_FAILURE(status))
         return throwTypeError(globalObject, scope, "failed to open field position iterator"_s);
 
-    status = U_ZERO_ERROR;
     Vector<UChar, 32> result(32);
     auto resultLength = unum_formatDoubleForFields(m_numberFormat.get(), value, result.data(), result.size(), fieldItr.get(), &status);
     if (status == U_BUFFER_OVERFLOW_ERROR) {
@@ -531,45 +577,14 @@ JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double val
     if (U_FAILURE(status))
         return throwTypeError(globalObject, scope, "failed to format a number."_s);
 
-    int32_t literalFieldType = -1;
-    auto literalField = IntlNumberFormatField(literalFieldType, resultLength);
-    Vector<IntlNumberFormatField> fields(resultLength, literalField);
-    int32_t beginIndex = 0;
-    int32_t endIndex = 0;
-    auto fieldType = ufieldpositer_next(fieldItr.get(), &beginIndex, &endIndex);
-    while (fieldType >= 0) {
-        auto size = endIndex - beginIndex;
-        for (auto i = beginIndex; i < endIndex; ++i) {
-            // Only override previous value if new value is more specific.
-            if (fields[i].size >= size)
-                fields[i] = IntlNumberFormatField(fieldType, size);
-        }
-        fieldType = ufieldpositer_next(fieldItr.get(), &beginIndex, &endIndex);
-    }
+    auto resultString = String(result.data(), resultLength);
 
     JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
     if (!parts)
         return throwOutOfMemoryError(globalObject, scope);
-    unsigned index = 0;
 
-    auto resultString = String(result.data(), resultLength);
-    auto typePropertyName = Identifier::fromString(vm, "type");
-    auto literalString = jsNontrivialString(vm, "literal"_s);
-
-    int32_t currentIndex = 0;
-    while (currentIndex < resultLength) {
-        auto startIndex = currentIndex;
-        auto fieldType = fields[currentIndex].type;
-        while (currentIndex < resultLength && fields[currentIndex].type == fieldType)
-            ++currentIndex;
-        auto partType = fieldType == literalFieldType ? literalString : jsString(vm, partTypeString(UNumberFormatFields(fieldType), value));
-        auto partValue = jsSubstring(vm, resultString, startIndex, currentIndex - startIndex);
-        JSObject* part = constructEmptyObject(globalObject);
-        part->putDirect(vm, typePropertyName, partType);
-        part->putDirect(vm, vm.propertyNames->value, partValue);
-        parts->putDirectIndex(globalObject, index++, part);
-        RETURN_IF_EXCEPTION(scope, { });
-    }
+    formatToPartsInternal(globalObject, value, resultString, fieldItr.get(), parts);
+    RETURN_IF_EXCEPTION(scope, { });
 
     return parts;
 }
index 2b06f07..9f10b5d 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2015 Andy VanWagoner (andy@vanwagoner.family)
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -57,12 +58,14 @@ public:
     void initializeNumberFormat(JSGlobalObject*, JSValue locales, JSValue optionsValue);
     JSValue format(JSGlobalObject*, double);
     JSValue format(JSGlobalObject*, JSBigInt*);
-    JSValue formatToParts(JSGlobalObject*, double value);
+    JSValue formatToParts(JSGlobalObject*, double);
     JSObject* resolvedOptions(JSGlobalObject*);
 
     JSBoundFunction* boundFormat() const { return m_boundFormat.get(); }
     void setBoundFormat(VM&, JSBoundFunction*);
 
+    static void formatToPartsInternal(JSGlobalObject*, double, const String& formatted, UFieldPositionIterator*, JSArray*, JSString* unit = nullptr);
+
 protected:
     IntlNumberFormat(VM&, Structure*);
     void finishCreation(VM&);
index fb2bb5b..57b596d 100644 (file)
@@ -2,6 +2,7 @@
  * Copyright (C) 2015 Andy VanWagoner (andy@vanwagoner.family)
  * Copyright (C) 2015 Sukolsak Sakshuwong (sukolsak@gmail.com)
  * Copyright (C) 2016-2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #include "IntlNumberFormatPrototype.h"
 #include "IntlPluralRulesConstructor.h"
 #include "IntlPluralRulesPrototype.h"
+#include "IntlRelativeTimeFormatConstructor.h"
+#include "IntlRelativeTimeFormatPrototype.h"
 #include "JSCInlines.h"
 #include "JSCJSValueInlines.h"
 #include "Lookup.h"
 #include "ObjectPrototype.h"
 #include "Options.h"
 #include <unicode/ucol.h>
-#include <unicode/udat.h>
 #include <unicode/uloc.h>
-#include <unicode/unum.h>
 #include <unicode/unumsys.h>
 #include <wtf/Assertions.h>
 #include <wtf/Language.h>
@@ -114,17 +115,26 @@ struct MatcherResult {
 const ClassInfo IntlObject::s_info = { "Object", &Base::s_info, &intlObjectTable, nullptr, CREATE_METHOD_TABLE(IntlObject) };
 
 IntlObject::IntlObject(VM& vm, Structure* structure)
-    : JSNonFinalObject(vm, structure)
+    : Base(vm, structure)
 {
 }
 
-IntlObject* IntlObject::create(VM& vm, Structure* structure)
+IntlObject* IntlObject::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
 {
     IntlObject* object = new (NotNull, allocateCell<IntlObject>(vm.heap)) IntlObject(vm, structure);
-    object->finishCreation(vm);
+    object->finishCreation(vm, globalObject);
     return object;
 }
 
+void IntlObject::finishCreation(VM& vm, JSGlobalObject* globalObject)
+{
+    Base::finishCreation(vm);
+    if (Options::useIntlRelativeTimeFormat()) {
+        auto* relativeTimeFormatConstructor = IntlRelativeTimeFormatConstructor::create(vm, IntlRelativeTimeFormatConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()), jsCast<IntlRelativeTimeFormatPrototype*>(globalObject->relativeTimeFormatStructure()->storedPrototypeObject()));
+        putDirectWithoutTransition(vm, vm.propertyNames->RelativeTimeFormat, relativeTimeFormatConstructor, static_cast<unsigned>(PropertyAttribute::DontEnum));
+    }
+}
+
 Structure* IntlObject::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
 {
     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
@@ -164,26 +174,7 @@ static void addMissingScriptLocales(HashSet<String>& availableLocales)
         availableLocales.add(zh_TW_String.get());
 }
 
-const HashSet<String>& intlCollatorAvailableLocales()
-{
-    static NeverDestroyed<HashSet<String>> cachedAvailableLocales;
-    HashSet<String>& availableLocales = cachedAvailableLocales.get();
-
-    static std::once_flag initializeOnce;
-    std::call_once(initializeOnce, [&] {
-        ASSERT(availableLocales.isEmpty());
-        int32_t count = ucol_countAvailable();
-        for (int32_t i = 0; i < count; ++i) {
-            String locale = convertICULocaleToBCP47LanguageTag(ucol_getAvailable(i));
-            if (!locale.isEmpty())
-                availableLocales.add(locale);
-        }
-        addMissingScriptLocales(availableLocales);
-    });
-    return availableLocales;
-}
-
-const HashSet<String>& intlDateTimeFormatAvailableLocales()
+const HashSet<String>& intlAvailableLocales()
 {
     static NeverDestroyed<HashSet<String>> cachedAvailableLocales;
     HashSet<String>& availableLocales = cachedAvailableLocales.get();
@@ -191,9 +182,9 @@ const HashSet<String>& intlDateTimeFormatAvailableLocales()
     static std::once_flag initializeOnce;
     std::call_once(initializeOnce, [&] {
         ASSERT(availableLocales.isEmpty());
-        int32_t count = udat_countAvailable();
+        int32_t count = uloc_countAvailable();
         for (int32_t i = 0; i < count; ++i) {
-            String locale = convertICULocaleToBCP47LanguageTag(udat_getAvailable(i));
+            String locale = convertICULocaleToBCP47LanguageTag(uloc_getAvailable(i));
             if (!locale.isEmpty())
                 availableLocales.add(locale);
         }
@@ -202,7 +193,7 @@ const HashSet<String>& intlDateTimeFormatAvailableLocales()
     return availableLocales;
 }
 
-const HashSet<String>& intlNumberFormatAvailableLocales()
+const HashSet<String>& intlCollatorAvailableLocales()
 {
     static NeverDestroyed<HashSet<String>> cachedAvailableLocales;
     HashSet<String>& availableLocales = cachedAvailableLocales.get();
@@ -210,9 +201,9 @@ const HashSet<String>& intlNumberFormatAvailableLocales()
     static std::once_flag initializeOnce;
     std::call_once(initializeOnce, [&] {
         ASSERT(availableLocales.isEmpty());
-        int32_t count = unum_countAvailable();
+        int32_t count = ucol_countAvailable();
         for (int32_t i = 0; i < count; ++i) {
-            String locale = convertICULocaleToBCP47LanguageTag(unum_getAvailable(i));
+            String locale = convertICULocaleToBCP47LanguageTag(ucol_getAvailable(i));
             if (!locale.isEmpty())
                 availableLocales.add(locale);
         }
@@ -328,13 +319,15 @@ bool isUnicodeLocaleIdentifierType(StringView string)
 {
     ASSERT(!string.isNull());
 
-    auto length = string.length();
-    if (length < 3 || length > 8)
-        return false;
-
-    for (auto character : string.codeUnits()) {
-        if (!isASCIIAlphanumeric(character))
+    for (auto part : string.splitAllowingEmptyEntries('-')) {
+        auto length = part.length();
+        if (length < 3 || length > 8)
             return false;
+
+        for (auto character : part.codeUnits()) {
+            if (!isASCIIAlphanumeric(character))
+                return false;
+        }
     }
 
     return true;
@@ -979,8 +972,6 @@ JSValue supportedLocales(JSGlobalObject* globalObject, const HashSet<String>& av
     RETURN_IF_EXCEPTION(scope, JSValue());
 
     PropertyDescriptor desc;
-    desc.setConfigurable(false);
-    desc.setWritable(false);
 
     size_t len = keys.size();
     for (size_t i = 0; i < len; ++i) {
index 60c0025..6af6f73 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2015 Andy VanWagoner (andy@vanwagoner.family)
  * Copyright (C) 2019-2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -43,20 +44,23 @@ public:
         return &vm.plainObjectSpace;
     }
 
-    static IntlObject* create(VM&, Structure*);
+    static IntlObject* create(VM&, JSGlobalObject*, Structure*);
     static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
 
     DECLARE_INFO;
 
 private:
     IntlObject(VM&, Structure*);
+    void finishCreation(VM&, JSGlobalObject*);
 };
 
 String defaultLocale(JSGlobalObject*);
+const HashSet<String>& intlAvailableLocales();
 const HashSet<String>& intlCollatorAvailableLocales();
-const HashSet<String>& intlDateTimeFormatAvailableLocales();
-const HashSet<String>& intlNumberFormatAvailableLocales();
-inline const HashSet<String>& intlPluralRulesAvailableLocales() { return intlNumberFormatAvailableLocales(); }
+inline const HashSet<String>& intlDateTimeFormatAvailableLocales() { return intlAvailableLocales(); }
+inline const HashSet<String>& intlNumberFormatAvailableLocales() { return intlAvailableLocales(); }
+inline const HashSet<String>& intlPluralRulesAvailableLocales() { return intlAvailableLocales(); }
+inline const HashSet<String>& intlRelativeTimeFormatAvailableLocales() { return intlAvailableLocales(); }
 
 bool intlBooleanOption(JSGlobalObject*, JSValue options, PropertyName, bool& usesFallback);
 String intlStringOption(JSGlobalObject*, JSValue options, PropertyName, std::initializer_list<const char*> values, const char* notFound, const char* fallback);
index c0b6e4e..8771601 100644 (file)
@@ -127,7 +127,7 @@ void IntlPluralRules::initializePluralRules(JSGlobalObject* globalObject, JSValu
         return;
     }
 
-    String typeString = intlStringOption(globalObject, options, Identifier::fromString(vm, "type"), { "cardinal", "ordinal" }, "type must be \"cardinal\" or \"ordinal\"", "cardinal");
+    String typeString = intlStringOption(globalObject, options, vm.propertyNames->type, { "cardinal", "ordinal" }, "type must be \"cardinal\" or \"ordinal\"", "cardinal");
     RETURN_IF_EXCEPTION(scope, void());
     m_type = typeString == "ordinal" ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL;
 
@@ -201,7 +201,7 @@ JSObject* IntlPluralRules::resolvedOptions(JSGlobalObject* globalObject)
 
     JSObject* options = constructEmptyObject(globalObject);
     options->putDirect(vm, vm.propertyNames->locale, jsNontrivialString(vm, m_locale));
-    options->putDirect(vm, Identifier::fromString(vm, "type"), jsNontrivialString(vm, m_type == UPLURAL_TYPE_ORDINAL ? "ordinal"_s : "cardinal"_s));
+    options->putDirect(vm, vm.propertyNames->type, jsNontrivialString(vm, m_type == UPLURAL_TYPE_ORDINAL ? "ordinal"_s : "cardinal"_s));
     options->putDirect(vm, Identifier::fromString(vm, "minimumIntegerDigits"), jsNumber(m_minimumIntegerDigits));
     options->putDirect(vm, Identifier::fromString(vm, "minimumFractionDigits"), jsNumber(m_minimumFractionDigits));
     options->putDirect(vm, Identifier::fromString(vm, "maximumFractionDigits"), jsNumber(m_maximumFractionDigits));
diff --git a/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp b/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp
new file mode 100644 (file)
index 0000000..e831612
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IntlRelativeTimeFormat.h"
+
+#include "Error.h"
+#include "IntlNumberFormat.h"
+#include "IntlObject.h"
+#include "JSCInlines.h"
+#include "ObjectConstructor.h"
+
+namespace JSC {
+
+const ClassInfo IntlRelativeTimeFormat::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlRelativeTimeFormat) };
+
+constexpr const char* relevantExtensionKeys[1] = { "nu" };
+
+struct UFieldPositionIteratorDeleter {
+    void operator()(UFieldPositionIterator* iterator) const
+    {
+        if (iterator)
+            ufieldpositer_close(iterator);
+    }
+};
+
+void IntlRelativeTimeFormat::URelativeDateTimeFormatterDeleter::operator()(URelativeDateTimeFormatter* relativeDateTimeFormatter) const
+{
+    if (relativeDateTimeFormatter)
+        ureldatefmt_close(relativeDateTimeFormatter);
+}
+
+void IntlRelativeTimeFormat::UNumberFormatDeleter::operator()(UNumberFormat* numberFormat) const
+{
+    if (numberFormat)
+        unum_close(numberFormat);
+}
+
+IntlRelativeTimeFormat* IntlRelativeTimeFormat::create(VM& vm, Structure* structure)
+{
+    auto* format = new (NotNull, allocateCell<IntlRelativeTimeFormat>(vm.heap)) IntlRelativeTimeFormat(vm, structure);
+    format->finishCreation(vm);
+    return format;
+}
+
+Structure* IntlRelativeTimeFormat::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+{
+    return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
+}
+
+IntlRelativeTimeFormat::IntlRelativeTimeFormat(VM& vm, Structure* structure)
+    : Base(vm, structure)
+{
+}
+
+void IntlRelativeTimeFormat::finishCreation(VM& vm)
+{
+    Base::finishCreation(vm);
+    ASSERT(inherits(vm, info()));
+}
+
+void IntlRelativeTimeFormat::visitChildren(JSCell* cell, SlotVisitor& visitor)
+{
+    auto* thisObject = jsCast<IntlRelativeTimeFormat*>(cell);
+    ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+
+    Base::visitChildren(thisObject, visitor);
+}
+
+static Vector<String> localeData(const String& locale, size_t keyIndex)
+{
+    // The index of the extension key "nu" in relevantExtensionKeys is 0.
+    ASSERT_UNUSED(keyIndex, !keyIndex);
+    return numberingSystemsForLocale(locale);
+}
+
+// https://tc39.es/ecma402/#sec-InitializeRelativeTimeFormat
+void IntlRelativeTimeFormat::initializeRelativeTimeFormat(JSGlobalObject* globalObject, JSValue locales, JSValue optionsValue)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    Vector<String> requestedLocales = canonicalizeLocaleList(globalObject, locales);
+    RETURN_IF_EXCEPTION(scope, void());
+
+    JSObject* options;
+    if (optionsValue.isUndefined())
+        options = constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure());
+    else {
+        options = optionsValue.toObject(globalObject);
+        RETURN_IF_EXCEPTION(scope, void());
+    }
+
+    HashMap<String, String> opt;
+    String localeMatcher = intlStringOption(globalObject, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit");
+    RETURN_IF_EXCEPTION(scope, void());
+    opt.add(vm.propertyNames->localeMatcher.string(), localeMatcher);
+
+    String numberingSystem = intlStringOption(globalObject, options, vm.propertyNames->numberingSystem, { }, nullptr, nullptr);
+    RETURN_IF_EXCEPTION(scope, void());
+    if (!numberingSystem.isNull()) {
+        if (!isUnicodeLocaleIdentifierType(numberingSystem)) {
+            throwRangeError(globalObject, scope, "numberingSystem is not a well-formed numbering system value"_s);
+            return;
+        }
+        opt.add("nu"_s, numberingSystem);
+    }
+
+    const HashSet<String>& availableLocales = intlRelativeTimeFormatAvailableLocales();
+    HashMap<String, String> resolved = resolveLocale(globalObject, availableLocales, requestedLocales, opt, relevantExtensionKeys, WTF_ARRAY_LENGTH(relevantExtensionKeys), localeData);
+    m_locale = resolved.get(vm.propertyNames->locale.string());
+    if (m_locale.isEmpty()) {
+        throwTypeError(globalObject, scope, "failed to initialize RelativeTimeFormat due to invalid locale"_s);
+        return;
+    }
+
+    m_numberingSystem = resolved.get("nu"_s);
+    CString dataLocaleWithExtensions = makeString(resolved.get("dataLocale"_s), "-u-nu-", m_numberingSystem).utf8();
+
+    String style = intlStringOption(globalObject, options, vm.propertyNames->style, { "long", "short", "narrow" }, "style must be either \"long\", \"short\", or \"narrow\"", "long");
+    RETURN_IF_EXCEPTION(scope, void());
+    if (style == "long")
+        m_style = UDAT_STYLE_LONG;
+    else if (style == "short")
+        m_style = UDAT_STYLE_SHORT;
+    else if (style == "narrow")
+        m_style = UDAT_STYLE_NARROW;
+    else
+        ASSERT_NOT_REACHED();
+
+    String numeric = intlStringOption(globalObject, options, vm.propertyNames->numeric, { "always", "auto" }, "numeric must be either \"always\" or \"auto\"", "always");
+    RETURN_IF_EXCEPTION(scope, void());
+    m_numeric = (numeric == "always");
+
+    UErrorCode status = U_ZERO_ERROR;
+    m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(UNUM_DECIMAL, nullptr, 0, dataLocaleWithExtensions.data(), nullptr, &status));
+    if (UNLIKELY(U_FAILURE(status))) {
+        throwTypeError(globalObject, scope, "failed to initialize RelativeTimeFormat"_s);
+        return;
+    }
+
+    m_relativeDateTimeFormatter = std::unique_ptr<URelativeDateTimeFormatter, URelativeDateTimeFormatterDeleter>(ureldatefmt_open(dataLocaleWithExtensions.data(), m_numberFormat.get(), m_style, UDISPCTX_CAPITALIZATION_FOR_STANDALONE, &status));
+    if (UNLIKELY(U_FAILURE(status))) {
+        throwTypeError(globalObject, scope, "failed to initialize RelativeTimeFormat"_s);
+        return;
+    }
+}
+
+static ASCIILiteral styleString(UDateRelativeDateTimeFormatterStyle style)
+{
+    switch (style) {
+    case UDAT_STYLE_LONG:
+        return "long"_s;
+    case UDAT_STYLE_SHORT:
+        return "short"_s;
+    case UDAT_STYLE_NARROW:
+        return "narrow"_s;
+    }
+    ASSERT_NOT_REACHED();
+    return ASCIILiteral::null();
+}
+
+static ASCIILiteral numericString(bool numeric)
+{
+    return numeric ? "always"_s : "auto"_s;
+}
+
+// https://tc39.es/ecma402/#sec-intl.relativetimeformat.prototype.resolvedoptions
+JSObject* IntlRelativeTimeFormat::resolvedOptions(JSGlobalObject* globalObject)
+{
+    VM& vm = globalObject->vm();
+    JSObject* options = constructEmptyObject(globalObject);
+    options->putDirect(vm, vm.propertyNames->locale, jsNontrivialString(vm, m_locale));
+    options->putDirect(vm, vm.propertyNames->style, jsNontrivialString(vm, styleString(m_style)));
+    options->putDirect(vm, vm.propertyNames->numeric, jsNontrivialString(vm, numericString(m_numeric)));
+    options->putDirect(vm, vm.propertyNames->numberingSystem, jsNontrivialString(vm, m_numberingSystem));
+    return options;
+}
+
+static StringView singularUnit(StringView unit)
+{
+    // Plurals are allowed, but thankfully they're all just a simple -s.
+    return unit.endsWith("s") ? unit.left(unit.length() - 1) : unit;
+}
+
+// https://tc39.es/ecma402/#sec-singularrelativetimeunit
+static Optional<URelativeDateTimeUnit> relativeTimeUnitType(StringView unit)
+{
+    StringView singular = singularUnit(unit);
+
+    if (singular == "second")
+        return UDAT_REL_UNIT_SECOND;
+    if (singular == "minute")
+        return UDAT_REL_UNIT_MINUTE;
+    if (singular == "hour")
+        return UDAT_REL_UNIT_HOUR;
+    if (singular == "day")
+        return UDAT_REL_UNIT_DAY;
+    if (singular == "week")
+        return UDAT_REL_UNIT_WEEK;
+    if (singular == "month")
+        return UDAT_REL_UNIT_MONTH;
+    if (singular == "quarter")
+        return UDAT_REL_UNIT_QUARTER;
+    if (singular == "year")
+        return UDAT_REL_UNIT_YEAR;
+
+    return WTF::nullopt;
+}
+
+String IntlRelativeTimeFormat::formatInternal(JSGlobalObject* globalObject, double value, StringView unit)
+{
+    ASSERT(m_relativeDateTimeFormatter);
+
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    if (!std::isfinite(value)) {
+        throwRangeError(globalObject, scope, "number argument must be finite"_s);
+        return String();
+    }
+
+    auto unitType = relativeTimeUnitType(unit);
+    if (!unitType) {
+        throwRangeError(globalObject, scope, "unit argument is not a recognized unit type"_s);
+        return String();
+    }
+
+    auto formatRelativeTime = m_numeric ? ureldatefmt_formatNumeric : ureldatefmt_format;
+
+    UErrorCode status = U_ZERO_ERROR;
+    Vector<UChar, 32> result(32);
+    auto resultLength = formatRelativeTime(m_relativeDateTimeFormatter.get(), value, unitType.value(), result.data(), result.size(), &status);
+    if (status == U_BUFFER_OVERFLOW_ERROR) {
+        status = U_ZERO_ERROR;
+        result.grow(resultLength);
+        formatRelativeTime(m_relativeDateTimeFormatter.get(), value, unitType.value(), result.data(), resultLength, &status);
+    }
+    if (UNLIKELY(U_FAILURE(status))) {
+        throwTypeError(globalObject, scope, "failed to format relative time"_s);
+        return String();
+    }
+
+    return String(result.data(), resultLength);
+}
+
+// https://tc39.es/ecma402/#sec-FormatRelativeTime
+JSValue IntlRelativeTimeFormat::format(JSGlobalObject* globalObject, double value, StringView unit)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    String result = formatInternal(globalObject, value, unit);
+    RETURN_IF_EXCEPTION(scope, { });
+
+    return jsString(vm, result);
+}
+
+// https://tc39.es/ecma402/#sec-FormatRelativeTimeToParts
+JSValue IntlRelativeTimeFormat::formatToParts(JSGlobalObject* globalObject, double value, StringView unit)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    String formattedRelativeTime = formatInternal(globalObject, value, unit);
+    RETURN_IF_EXCEPTION(scope, { });
+
+    UErrorCode status = U_ZERO_ERROR;
+    auto iterator = std::unique_ptr<UFieldPositionIterator, UFieldPositionIteratorDeleter>(ufieldpositer_open(&status));
+    ASSERT(U_SUCCESS(status));
+
+    double absValue = std::abs(value);
+
+    Vector<UChar, 32> buffer(32);
+    auto numberLength = unum_formatDoubleForFields(m_numberFormat.get(), absValue, buffer.data(), buffer.size(), iterator.get(), &status);
+    if (status == U_BUFFER_OVERFLOW_ERROR) {
+        status = U_ZERO_ERROR;
+        buffer.grow(numberLength);
+        unum_formatDoubleForFields(m_numberFormat.get(), absValue, buffer.data(), numberLength, iterator.get(), &status);
+    }
+    if (U_FAILURE(status))
+        return throwTypeError(globalObject, scope, "failed to format relative time"_s);
+
+    auto formattedNumber = String(buffer.data(), numberLength);
+
+    JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
+    if (!parts)
+        return throwOutOfMemoryError(globalObject, scope);
+
+    JSString* literalString = jsNontrivialString(vm, "literal"_s);
+
+    // We only have one input number, so our relative time will have at most one numeric substring,
+    // but we need to list all of the numeric parts separately.
+    size_t numberEnd = 0;
+    size_t numberStart = formattedRelativeTime.find(formattedNumber);
+    if (numberStart != notFound) {
+        numberEnd = numberStart + numberLength;
+
+        // Add initial literal if there is one.
+        if (numberStart) {
+            JSObject* part = constructEmptyObject(globalObject);
+            part->putDirect(vm, vm.propertyNames->type, literalString);
+            part->putDirect(vm, vm.propertyNames->value, jsSubstring(vm, formattedRelativeTime, 0, numberStart));
+            parts->push(globalObject, part);
+            RETURN_IF_EXCEPTION(scope, { });
+        }
+
+        IntlNumberFormat::formatToPartsInternal(globalObject, absValue, formattedNumber, iterator.get(), parts, jsString(vm, singularUnit(unit).toString()));
+        RETURN_IF_EXCEPTION(scope, { });
+    }
+
+    // Add final literal if there is one.
+    auto stringLength = formattedRelativeTime.length();
+    if (numberEnd != stringLength) {
+        JSObject* part = constructEmptyObject(globalObject);
+        part->putDirect(vm, vm.propertyNames->type, literalString);
+        part->putDirect(vm, vm.propertyNames->value, jsSubstring(vm, formattedRelativeTime, numberEnd, stringLength - numberEnd));
+        parts->push(globalObject, part);
+        RETURN_IF_EXCEPTION(scope, { });
+    }
+
+    return parts;
+}
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.h b/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.h
new file mode 100644 (file)
index 0000000..e721930
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "JSObject.h"
+#include <unicode/ufieldpositer.h>
+#include <unicode/ureldatefmt.h>
+
+namespace JSC {
+
+class IntlRelativeTimeFormat final : public JSNonFinalObject {
+public:
+    using Base = JSNonFinalObject;
+
+    static constexpr bool needsDestruction = true;
+
+    static void destroy(JSCell* cell)
+    {
+        static_cast<IntlRelativeTimeFormat*>(cell)->IntlRelativeTimeFormat::~IntlRelativeTimeFormat();
+    }
+
+    template<typename CellType, SubspaceAccess mode>
+    static IsoSubspace* subspaceFor(VM& vm)
+    {
+        return vm.intlRelativeTimeFormatSpace<mode>();
+    }
+
+    static IntlRelativeTimeFormat* create(VM&, Structure*);
+    static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
+
+    DECLARE_INFO;
+
+    void initializeRelativeTimeFormat(JSGlobalObject*, JSValue locales, JSValue options);
+    JSValue format(JSGlobalObject*, double, StringView unitString);
+    JSValue formatToParts(JSGlobalObject*, double, StringView unitString);
+    JSObject* resolvedOptions(JSGlobalObject*);
+
+private:
+    IntlRelativeTimeFormat(VM&, Structure*);
+    void finishCreation(VM&);
+    static void visitChildren(JSCell*, SlotVisitor&);
+
+    String formatInternal(JSGlobalObject*, double, StringView unit);
+
+    struct URelativeDateTimeFormatterDeleter {
+        void operator()(URelativeDateTimeFormatter*) const;
+    };
+    struct UNumberFormatDeleter {
+        void operator()(UNumberFormat*) const;
+    };
+
+    std::unique_ptr<URelativeDateTimeFormatter, URelativeDateTimeFormatterDeleter> m_relativeDateTimeFormatter;
+    std::unique_ptr<UNumberFormat, UNumberFormatDeleter> m_numberFormat;
+
+    String m_locale;
+    String m_numberingSystem;
+    UDateRelativeDateTimeFormatterStyle m_style { UDAT_STYLE_LONG };
+    bool m_numeric { true };
+};
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlRelativeTimeFormatConstructor.cpp b/Source/JavaScriptCore/runtime/IntlRelativeTimeFormatConstructor.cpp
new file mode 100644 (file)
index 0000000..8fa5d05
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IntlRelativeTimeFormatConstructor.h"
+
+#include "Error.h"
+#include "IntlObject.h"
+#include "IntlRelativeTimeFormat.h"
+#include "IntlRelativeTimeFormatPrototype.h"
+#include "JSCInlines.h"
+#include "Lookup.h"
+
+namespace JSC {
+
+STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(IntlRelativeTimeFormatConstructor);
+
+static EncodedJSValue JSC_HOST_CALL IntlRelativeTimeFormatConstructorFuncSupportedLocalesOf(JSGlobalObject*, CallFrame*);
+
+}
+
+#include "IntlRelativeTimeFormatConstructor.lut.h"
+
+namespace JSC {
+
+const ClassInfo IntlRelativeTimeFormatConstructor::s_info = { "Function", &InternalFunction::s_info, &relativeTimeFormatConstructorTable, nullptr, CREATE_METHOD_TABLE(IntlRelativeTimeFormatConstructor) };
+
+/* Source for IntlRelativeTimeFormatConstructor.lut.h
+@begin relativeTimeFormatConstructorTable
+  supportedLocalesOf             IntlRelativeTimeFormatConstructorFuncSupportedLocalesOf             DontEnum|Function 1
+@end
+*/
+
+IntlRelativeTimeFormatConstructor* IntlRelativeTimeFormatConstructor::create(VM& vm, Structure* structure, IntlRelativeTimeFormatPrototype* relativeTimeFormatPrototype)
+{
+    auto* constructor = new (NotNull, allocateCell<IntlRelativeTimeFormatConstructor>(vm.heap)) IntlRelativeTimeFormatConstructor(vm, structure);
+    constructor->finishCreation(vm, relativeTimeFormatPrototype);
+    return constructor;
+}
+
+Structure* IntlRelativeTimeFormatConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+{
+    return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info());
+}
+
+static EncodedJSValue JSC_HOST_CALL callIntlRelativeTimeFormat(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL constructIntlRelativeTimeFormat(JSGlobalObject*, CallFrame*);
+
+IntlRelativeTimeFormatConstructor::IntlRelativeTimeFormatConstructor(VM& vm, Structure* structure)
+    : Base(vm, structure, callIntlRelativeTimeFormat, constructIntlRelativeTimeFormat)
+{
+}
+
+void IntlRelativeTimeFormatConstructor::finishCreation(VM& vm, IntlRelativeTimeFormatPrototype* relativeTimeFormatPrototype)
+{
+    Base::finishCreation(vm, "RelativeTimeFormat"_s, NameAdditionMode::WithoutStructureTransition);
+    putDirectWithoutTransition(vm, vm.propertyNames->prototype, relativeTimeFormatPrototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
+    putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(0), PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum);
+    relativeTimeFormatPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, this, static_cast<unsigned>(PropertyAttribute::DontEnum));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.RelativeTimeFormat
+static EncodedJSValue JSC_HOST_CALL constructIntlRelativeTimeFormat(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    Structure* structure = InternalFunction::createSubclassStructure(globalObject, callFrame->jsCallee(), callFrame->newTarget(), jsCast<IntlRelativeTimeFormatConstructor*>(callFrame->jsCallee())->relativeTimeFormatStructure(vm));
+    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+    IntlRelativeTimeFormat* relativeTimeFormat = IntlRelativeTimeFormat::create(vm, structure);
+    ASSERT(relativeTimeFormat);
+
+    scope.release();
+    relativeTimeFormat->initializeRelativeTimeFormat(globalObject, callFrame->argument(0), callFrame->argument(1));
+    return JSValue::encode(relativeTimeFormat);
+}
+
+// https://tc39.es/ecma402/#sec-Intl.RelativeTimeFormat
+static EncodedJSValue JSC_HOST_CALL callIntlRelativeTimeFormat(JSGlobalObject* globalObject, CallFrame*)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    return JSValue::encode(throwConstructorCannotBeCalledAsFunctionTypeError(globalObject, scope, "RelativeTimeFormat"));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.RelativeTimeFormat.supportedLocalesOf
+EncodedJSValue JSC_HOST_CALL IntlRelativeTimeFormatConstructorFuncSupportedLocalesOf(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto& availableLocales = intlRelativeTimeFormatAvailableLocales();
+
+    auto requestedLocales = canonicalizeLocaleList(globalObject, callFrame->argument(0));
+    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(supportedLocales(globalObject, availableLocales, requestedLocales, callFrame->argument(1))));
+}
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlRelativeTimeFormatConstructor.h b/Source/JavaScriptCore/runtime/IntlRelativeTimeFormatConstructor.h
new file mode 100644 (file)
index 0000000..c8ccff2
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "InternalFunction.h"
+#include "IntlObject.h"
+
+namespace JSC {
+
+class IntlRelativeTimeFormatPrototype;
+
+class IntlRelativeTimeFormatConstructor final : public InternalFunction {
+public:
+    using Base = InternalFunction;
+    static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable;
+
+    static IntlRelativeTimeFormatConstructor* create(VM&, Structure*, IntlRelativeTimeFormatPrototype*);
+    static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
+
+    DECLARE_INFO;
+
+    Structure* relativeTimeFormatStructure(VM&) const { return globalObject()->relativeTimeFormatStructure(); }
+
+protected:
+    void finishCreation(VM&, IntlRelativeTimeFormatPrototype*);
+
+private:
+    IntlRelativeTimeFormatConstructor(VM&, Structure*);
+};
+STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(IntlRelativeTimeFormatConstructor, InternalFunction);
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlRelativeTimeFormatPrototype.cpp b/Source/JavaScriptCore/runtime/IntlRelativeTimeFormatPrototype.cpp
new file mode 100644 (file)
index 0000000..b4a4d66
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IntlRelativeTimeFormatPrototype.h"
+
+#include "Error.h"
+#include "IntlRelativeTimeFormat.h"
+#include "JSCInlines.h"
+#include "JSObjectInlines.h"
+
+namespace JSC {
+
+static EncodedJSValue JSC_HOST_CALL IntlRelativeTimeFormatPrototypeFuncFormat(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlRelativeTimeFormatPrototypeFuncFormatToParts(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlRelativeTimeFormatPrototypeFuncResolvedOptions(JSGlobalObject*, CallFrame*);
+
+}
+
+#include "IntlRelativeTimeFormatPrototype.lut.h"
+
+namespace JSC {
+
+const ClassInfo IntlRelativeTimeFormatPrototype::s_info = { "Object", &Base::s_info, &relativeTimeFormatPrototypeTable, nullptr, CREATE_METHOD_TABLE(IntlRelativeTimeFormatPrototype) };
+
+/* Source for IntlRelativeTimeFormatPrototype.lut.h
+@begin relativeTimeFormatPrototypeTable
+  format           IntlRelativeTimeFormatPrototypeFuncFormat           DontEnum|Function 2
+  formatToParts    IntlRelativeTimeFormatPrototypeFuncFormatToParts    DontEnum|Function 2
+  resolvedOptions  IntlRelativeTimeFormatPrototypeFuncResolvedOptions  DontEnum|Function 0
+@end
+*/
+
+IntlRelativeTimeFormatPrototype* IntlRelativeTimeFormatPrototype::create(VM& vm, Structure* structure)
+{
+    auto* object = new (NotNull, allocateCell<IntlRelativeTimeFormatPrototype>(vm.heap)) IntlRelativeTimeFormatPrototype(vm, structure);
+    object->finishCreation(vm);
+    return object;
+}
+
+Structure* IntlRelativeTimeFormatPrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+{
+    return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
+}
+
+IntlRelativeTimeFormatPrototype::IntlRelativeTimeFormatPrototype(VM& vm, Structure* structure)
+    : Base(vm, structure)
+{
+}
+
+void IntlRelativeTimeFormatPrototype::finishCreation(VM& vm)
+{
+    Base::finishCreation(vm);
+    putDirectWithoutTransition(vm, vm.propertyNames->toStringTagSymbol, jsNontrivialString(vm, "Intl.RelativeTimeFormat"_s), PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly);
+}
+
+// https://tc39.es/ecma402/#sec-Intl.RelativeTimeFormat.prototype.format
+EncodedJSValue JSC_HOST_CALL IntlRelativeTimeFormatPrototypeFuncFormat(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* relativeTimeFormat = jsDynamicCast<IntlRelativeTimeFormat*>(vm, callFrame->thisValue());
+    if (!relativeTimeFormat)
+        return JSValue::encode(throwTypeError(globalObject, scope, "Intl.RelativeTimeFormat.prototype.format called on value that's not an object initialized as a RelativeTimeFormat"_s));
+
+    double value = callFrame->argument(0).toNumber(globalObject);
+    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+    String unit = callFrame->argument(1).toWTFString(globalObject);
+    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(relativeTimeFormat->format(globalObject, value, unit)));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.RelativeTimeFormat.prototype.formatToParts
+EncodedJSValue JSC_HOST_CALL IntlRelativeTimeFormatPrototypeFuncFormatToParts(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* relativeTimeFormat = jsDynamicCast<IntlRelativeTimeFormat*>(vm, callFrame->thisValue());
+    if (!relativeTimeFormat)
+        return JSValue::encode(throwTypeError(globalObject, scope, "Intl.RelativeTimeFormat.prototype.formatToParts called on value that's not an object initialized as a RelativeTimeFormat"_s));
+
+    double value = callFrame->argument(0).toNumber(globalObject);
+    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+    String unit = callFrame->argument(1).toWTFString(globalObject);
+    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(relativeTimeFormat->formatToParts(globalObject, value, unit)));
+}
+
+// https://tc39.es/ecma402/#sec-intl.relativetimeformat.prototype.resolvedoptions
+EncodedJSValue JSC_HOST_CALL IntlRelativeTimeFormatPrototypeFuncResolvedOptions(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* relativeTimeFormat = jsDynamicCast<IntlRelativeTimeFormat*>(vm, callFrame->thisValue());
+    if (!relativeTimeFormat)
+        return JSValue::encode(throwTypeError(globalObject, scope, "Intl.RelativeTimeFormat.prototype.resolvedOptions called on value that's not an object initialized as a RelativeTimeFormat"_s));
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(relativeTimeFormat->resolvedOptions(globalObject)));
+}
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlRelativeTimeFormatPrototype.h b/Source/JavaScriptCore/runtime/IntlRelativeTimeFormatPrototype.h
new file mode 100644 (file)
index 0000000..ccbaea8
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "JSObject.h"
+
+namespace JSC {
+
+class IntlRelativeTimeFormatPrototype final : public JSNonFinalObject {
+public:
+    using Base = JSNonFinalObject;
+    static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable;
+
+    template<typename CellType, SubspaceAccess>
+    static IsoSubspace* subspaceFor(VM& vm)
+    {
+        STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(IntlRelativeTimeFormatPrototype, Base);
+        return &vm.plainObjectSpace;
+    }
+
+    static IntlRelativeTimeFormatPrototype* create(VM&, Structure*);
+    static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
+
+    DECLARE_INFO;
+
+protected:
+    void finishCreation(VM&);
+
+private:
+    IntlRelativeTimeFormatPrototype(VM&, Structure*);
+};
+
+} // namespace JSC
index 764e100..19a48fb 100644 (file)
@@ -86,6 +86,8 @@
 #include "IntlObject.h"
 #include "IntlPluralRules.h"
 #include "IntlPluralRulesPrototype.h"
+#include "IntlRelativeTimeFormat.h"
+#include "IntlRelativeTimeFormatPrototype.h"
 #include "IteratorPrototype.h"
 #include "JSAPIWrapperObject.h"
 #include "JSArrayBuffer.h"
@@ -959,6 +961,12 @@ capitalName ## Constructor* lowerName ## Constructor = featureFlag ? capitalName
             IntlPluralRulesPrototype* pluralRulesPrototype = IntlPluralRulesPrototype::create(init.vm, globalObject, IntlPluralRulesPrototype::createStructure(init.vm, globalObject, globalObject->objectPrototype()));
             init.set(IntlPluralRules::createStructure(init.vm, globalObject, pluralRulesPrototype));
         });
+    m_relativeTimeFormatStructure.initLater(
+        [] (const Initializer<Structure>& init) {
+            JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(init.owner);
+            IntlRelativeTimeFormatPrototype* relativeTimeFormatPrototype = IntlRelativeTimeFormatPrototype::create(init.vm, IntlRelativeTimeFormatPrototype::createStructure(init.vm, globalObject, globalObject->objectPrototype()));
+            init.set(IntlRelativeTimeFormat::createStructure(init.vm, globalObject, relativeTimeFormatPrototype));
+        });
     m_defaultCollator.initLater(
         [] (const Initializer<IntlCollator>& init) {
             JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(init.owner);
@@ -970,7 +978,7 @@ capitalName ## Constructor* lowerName ## Constructor = featureFlag ? capitalName
             init.set(collator);
         });
 
-    IntlObject* intl = IntlObject::create(vm, IntlObject::createStructure(vm, this, m_objectPrototype.get()));
+    IntlObject* intl = IntlObject::create(vm, this, IntlObject::createStructure(vm, this, m_objectPrototype.get()));
     putDirectWithoutTransition(vm, vm.propertyNames->Intl, intl, static_cast<unsigned>(PropertyAttribute::DontEnum));
 
     m_moduleLoader.initLater(
@@ -1769,6 +1777,7 @@ void JSGlobalObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
     thisObject->m_numberFormatStructure.visit(visitor);
     thisObject->m_dateTimeFormatStructure.visit(visitor);
     thisObject->m_pluralRulesStructure.visit(visitor);
+    thisObject->m_relativeTimeFormatStructure.visit(visitor);
 
     visitor.append(thisObject->m_nullGetterFunction);
     visitor.append(thisObject->m_nullSetterFunction);
index b37b8ae..a0c3b4a 100644 (file)
@@ -294,6 +294,7 @@ public:
     LazyProperty<JSGlobalObject, Structure> m_numberFormatStructure;
     LazyProperty<JSGlobalObject, Structure> m_dateTimeFormatStructure;
     LazyProperty<JSGlobalObject, Structure> m_pluralRulesStructure;
+    LazyProperty<JSGlobalObject, Structure> m_relativeTimeFormatStructure;
 
     WriteBarrier<NullGetterFunction> m_nullGetterFunction;
     WriteBarrier<NullSetterFunction> m_nullSetterFunction;
@@ -788,6 +789,7 @@ public:
     Structure* numberFormatStructure() { return m_numberFormatStructure.get(this); }
     Structure* dateTimeFormatStructure() { return m_dateTimeFormatStructure.get(this); }
     Structure* pluralRulesStructure() { return m_pluralRulesStructure.get(this); }
+    Structure* relativeTimeFormatStructure() { return m_relativeTimeFormatStructure.get(this); }
 
     JS_EXPORT_PRIVATE void setRemoteDebuggingEnabled(bool);
     JS_EXPORT_PRIVATE bool remoteDebuggingEnabled() const;
index 1ef850c..5420d2a 100644 (file)
@@ -484,6 +484,7 @@ constexpr bool enableWebAssemblyStreamingApi = false;
     v(Bool, useWebAssemblyMultiValues, true, Normal, "Allow types from the wasm mulit-values spec.") \
     v(Bool, useWeakRefs, false, Normal, "Expose the WeakRef constructor.") \
     v(Bool, useBigInt, true, Normal, "If true, we will enable BigInt support.") \
+    v(Bool, useIntlRelativeTimeFormat, false, Normal, "Expose the Intl.RelativeTimeFormat constructor.") \
     v(Bool, useArrayAllocationProfiling, true, Normal, "If true, we will use our normal array allocation profiling. If false, the allocation profile will always claim to be undecided.") \
     v(Bool, forcePolyProto, false, Normal, "If true, create_this will always create an object with a poly proto structure.") \
     v(Bool, forceMiniVMMode, false, Normal, "If true, it will force mini VM mode on.") \
index 5c0ee26..162e99d 100644 (file)
@@ -77,6 +77,7 @@
 #include "IntlDateTimeFormat.h"
 #include "IntlNumberFormat.h"
 #include "IntlPluralRules.h"
+#include "IntlRelativeTimeFormat.h"
 #include "IsoHeapCellType.h"
 #include "IsoInlinedHeapCellType.h"
 #include "JITCode.h"
@@ -340,6 +341,7 @@ VM::VM(VMType vmType, HeapType heapType)
     , intlDateTimeFormatHeapCellType(IsoHeapCellType::create<IntlDateTimeFormat>())
     , intlNumberFormatHeapCellType(IsoHeapCellType::create<IntlNumberFormat>())
     , intlPluralRulesHeapCellType(IsoHeapCellType::create<IntlPluralRules>())
+    , intlRelativeTimeFormatHeapCellType(IsoHeapCellType::create<IntlRelativeTimeFormat>())
 #if ENABLE(WEBASSEMBLY)
     , webAssemblyCodeBlockHeapCellType(IsoHeapCellType::create<JSWebAssemblyCodeBlock>())
     , webAssemblyFunctionHeapCellType(IsoHeapCellType::create<WebAssemblyFunction>())
@@ -1511,6 +1513,7 @@ DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlCollatorSpace, intlCollatorHeapCellT
 DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlDateTimeFormatSpace, intlDateTimeFormatHeapCellType.get(), IntlDateTimeFormat)
 DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlNumberFormatSpace, intlNumberFormatHeapCellType.get(), IntlNumberFormat)
 DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlPluralRulesSpace, intlPluralRulesHeapCellType.get(), IntlPluralRules)
+DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlRelativeTimeFormatSpace, intlRelativeTimeFormatHeapCellType.get(), IntlRelativeTimeFormat)
 #if ENABLE(WEBASSEMBLY)
 DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(jsToWasmICCalleeSpace, cellHeapCellType.get(), JSToWasmICCallee)
 DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(webAssemblyCodeBlockSpace, webAssemblyCodeBlockHeapCellType.get(), JSWebAssemblyCodeBlock) // Hash:0x9ad995cd
index 12862a4..5292933 100644 (file)
@@ -128,6 +128,7 @@ class IntlCollator;
 class IntlDateTimeFormat;
 class IntlNumberFormat;
 class IntlPluralRules;
+class IntlRelativeTimeFormat;
 class JSAPIGlobalObject;
 class JSAPIWrapperGlobalObject;
 class JSAPIWrapperObject;
@@ -401,6 +402,7 @@ public:
     std::unique_ptr<IsoHeapCellType> intlDateTimeFormatHeapCellType;
     std::unique_ptr<IsoHeapCellType> intlNumberFormatHeapCellType;
     std::unique_ptr<IsoHeapCellType> intlPluralRulesHeapCellType;
+    std::unique_ptr<IsoHeapCellType> intlRelativeTimeFormatHeapCellType;
 #if ENABLE(WEBASSEMBLY)
     std::unique_ptr<IsoHeapCellType> webAssemblyCodeBlockHeapCellType;
     std::unique_ptr<IsoHeapCellType> webAssemblyFunctionHeapCellType;
@@ -557,6 +559,7 @@ public:
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(intlDateTimeFormatSpace)
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(intlNumberFormatSpace)
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(intlPluralRulesSpace)
+    DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(intlRelativeTimeFormatSpace)
 #if ENABLE(WEBASSEMBLY)
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(jsToWasmICCalleeSpace)
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(webAssemblyCodeBlockSpace)
index 2c009b6..dd1d49c 100644 (file)
@@ -53,6 +53,7 @@
 #include "TypeProfilerLog.h"
 #include "VMInspector.h"
 #include "WasmCapabilities.h"
+#include <unicode/uversion.h>
 #include <wtf/Atomics.h>
 #include <wtf/CPUTime.h>
 #include <wtf/DataLog.h>
@@ -2977,6 +2978,13 @@ static EncodedJSValue JSC_HOST_CALL functionSetUserPreferredLanguages(JSGlobalOb
     return JSValue::encode(jsUndefined());
 }
 
+static EncodedJSValue JSC_HOST_CALL functionICUVersion(JSGlobalObject*, CallFrame*)
+{
+    UVersionInfo versionInfo;
+    u_getVersion(versionInfo);
+    return JSValue::encode(jsNumber(versionInfo[0]));
+}
+
 void JSDollarVM::finishCreation(VM& vm)
 {
     DollarVMAssertScope assertScope;
@@ -3114,6 +3122,7 @@ void JSDollarVM::finishCreation(VM& vm)
     addFunction(vm, "rejectPromiseAsHandled", functionRejectPromiseAsHandled, 1);
 
     addFunction(vm, "setUserPreferredLanguages", functionSetUserPreferredLanguages, 1);
+    addFunction(vm, "icuVersion", functionICUVersion, 0);
 
     m_objectDoingSideEffectPutWithoutCorrectSlotStatusStructure.set(vm, this, ObjectDoingSideEffectPutWithoutCorrectSlotStatus::createStructure(vm, globalObject, jsNull()));
 }
index 6803682..c4c11f2 100644 (file)
@@ -1,3 +1,13 @@
+2020-04-19  Ross Kirsling  <ross.kirsling@sony.com>
+
+        [ECMA-402] Intl.RelativeTimeFormat missing in WebKit
+        https://bugs.webkit.org/show_bug.cgi?id=209770
+
+        Reviewed by Darin Adler.
+
+        * Scripts/run-jsc-stress-tests:
+        Add runIntlRelativeTimeFormatEnabled.
+
 2020-04-19  Don Olmstead  <don.olmstead@sony.com>
 
         [CMake] Consolidate TestNetscapePlugin build
index b057993..221a0ba 100755 (executable)
@@ -710,6 +710,10 @@ def runBigIntEnabledBaseline(*optionalTestSpecificOptions)
     run("big-int-enabled-baseline", "--useBigInt=true", "--useDFGJIT=0", *optionalTestSpecificOptions)
 end
 
+def runIntlRelativeTimeFormatEnabled(*optionalTestSpecificOptions)
+    run("intl-relativetimeformat-enabled", "--useIntlRelativeTimeFormat=true" , *(FTL_OPTIONS + optionalTestSpecificOptions))
+end
+
 def runFTLNoCJIT(*optionalTestSpecificOptions)
     run("misc-ftl-no-cjit", *(FTL_OPTIONS + NO_CJIT_OPTIONS + optionalTestSpecificOptions))
 end