Bug 1329904 - Introduce mozIntl.DateTimeFormat with support for pattern. r?waldo draft
authorZibi Braniecki <gandalf@mozilla.com>
Thu, 26 Jan 2017 19:38:14 -0800
changeset 467525 a460b06981bcf5360dbb5e33f1ad8d5d9188a8f1
parent 467095 1e0e193b0812f68a12fbd69198552af62347af1e
child 543707 0882332bdec5541d7652146e26aeb49fc27e2c72
push id43200
push userzbraniecki@mozilla.com
push dateFri, 27 Jan 2017 22:38:13 +0000
reviewerswaldo
bugs1329904
milestone54.0a1
Bug 1329904 - Introduce mozIntl.DateTimeFormat with support for pattern. r?waldo MozReview-Commit-ID: IBnVGsg1uOT
js/src/builtin/Intl.cpp
js/src/builtin/Intl.h
js/src/builtin/Intl.js
js/src/jsfriendapi.h
js/src/shell/js.cpp
js/src/tests/Intl/DateTimeFormat/mozExtensions.js
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -7,16 +7,17 @@
 /*
  * The Intl module specified by standard ECMA-402,
  * ECMAScript Internationalization API Specification.
  */
 
 #include "builtin/Intl.h"
 
 #include "mozilla/Casting.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Range.h"
 
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
@@ -643,16 +644,17 @@ enum UDateFormatField {
 };
 
 enum UDateFormatStyle {
     UDAT_FULL,
     UDAT_LONG,
     UDAT_MEDIUM,
     UDAT_SHORT,
     UDAT_DEFAULT = UDAT_MEDIUM,
+    UDAT_NONE = -1,
     UDAT_PATTERN = -2,
     UDAT_IGNORE = UDAT_PATTERN
 };
 
 enum UDateFormatSymbolType {
     UDAT_ERAS,
     UDAT_MONTHS,
     UDAT_SHORT_MONTHS,
@@ -812,24 +814,28 @@ IntlInitialize(JSContext* cx, HandleObje
     MOZ_ASSERT(ignored.isUndefined(),
                "Unexpected return value from non-legacy Intl object initializer");
     return true;
 }
 
 static bool
 LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
                      HandleValue thisValue, HandleValue locales, HandleValue options,
-                     MutableHandleValue result)
+                     mozilla::Maybe<bool> mozExtensions, MutableHandleValue result)
 {
-    FixedInvokeArgs<4> args(cx);
+    InvokeArgs args(cx);
+    if (!args.init(cx, mozExtensions ? 5 : 4))
+        return false;
 
     args[0].setObject(*obj);
     args[1].set(thisValue);
     args[2].set(locales);
     args[3].set(options);
+    if (mozExtensions)
+        args[4].setBoolean(mozExtensions.value());
 
     RootedValue thisv(cx, NullValue());
     if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, result))
         return false;
 
     MOZ_ASSERT(result.isObject(), "Legacy Intl object initializer must return an object");
     return true;
 }
@@ -1448,17 +1454,17 @@ NumberFormat(JSContext* cx, const CallAr
     numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
 
     RootedValue thisValue(cx, construct ? ObjectValue(*numberFormat) : args.thisv());
     RootedValue locales(cx, args.get(0));
     RootedValue options(cx, args.get(1));
 
     // Step 3.
     return LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue,
-                                locales, options, args.rval());
+                                locales, options, mozilla::Nothing(), args.rval());
 }
 
 static bool
 NumberFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return NumberFormat(cx, args, args.isConstructing());
 }
@@ -2357,17 +2363,17 @@ static const JSPropertySpec dateTimeForm
 };
 
 /**
  * 12.2.1 Intl.DateTimeFormat([ locales [, options]])
  *
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool
-DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct)
+DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct, bool mozExtensions = false)
 {
     // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
 
     // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
     RootedObject proto(cx);
     if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
         return false;
 
@@ -2387,26 +2393,41 @@ DateTimeFormat(JSContext* cx, const Call
                                     PrivateValue(nullptr));
 
     RootedValue thisValue(cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv());
     RootedValue locales(cx, args.get(0));
     RootedValue options(cx, args.get(1));
 
     // Step 3.
     return LegacyIntlInitialize(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat,
-                                thisValue, locales, options, args.rval());
+                                thisValue, locales, options, mozilla::Some(mozExtensions),
+                                args.rval());
 }
 
 static bool
 DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return DateTimeFormat(cx, args, args.isConstructing());
 }
 
+static bool
+MozDateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Don't allow to call mozIntl.DateTimeFormat as a function. That way we
+    // don't need to worry how to handle the legacy initialization semantics
+    // when applied on mozIntl.DateTimeFormat.
+    if (!ThrowIfNotConstructing(cx, args, "mozIntl.DateTimeFormat"))
+        return false;
+
+    return DateTimeFormat(cx, args, true, true);
+}
+
 bool
 js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
     MOZ_ASSERT(!args.isConstructing());
     // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it
     // cannot be used with "new", but it still has to be treated as a
@@ -2422,20 +2443,22 @@ DateTimeFormatObject::finalize(FreeOp* f
     const Value& slot =
         obj->as<DateTimeFormatObject>().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT);
     if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate()))
         udat_close(df);
 }
 
 static JSObject*
 CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
-                              MutableHandleObject constructor)
+                              MutableHandleObject constructor, bool mozExtensions = false)
 {
     RootedFunction ctor(cx);
-    ctor = GlobalObject::createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
+    ctor = mozExtensions
+           ? GlobalObject::createConstructor(cx, &MozDateTimeFormat, cx->names().DateTimeFormat, 0)
+           : GlobalObject::createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
     if (!ctor)
         return nullptr;
 
     RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
     if (!proto)
         return nullptr;
 
     if (!LinkConstructorAndPrototype(cx, ctor, proto))
@@ -2458,16 +2481,28 @@ CreateDateTimeFormatPrototype(JSContext*
     if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0))
         return nullptr;
 
     constructor.set(ctor);
     return proto;
 }
 
 bool
+js::AddDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl)
+{
+    Handle<GlobalObject*> global = cx->global();
+
+    RootedObject dummy(cx);
+    if (!CreateDateTimeFormatPrototype(cx, intl, global, &dummy, true))
+        return false;
+
+    return true;
+}
+
+bool
 js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
     RootedValue result(cx);
     if (!intl_availableLocales(cx, udat_countAvailable, udat_getAvailable, &result))
         return false;
@@ -3028,16 +3063,100 @@ js::intl_patternForSkeleton(JSContext* c
     MOZ_ASSERT(size >= 0);
     JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size);
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 
+bool
+js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 3);
+    MOZ_ASSERT(args[0].isString());
+
+    JSAutoByteString locale(cx, args[0].toString());
+    if (!locale)
+        return false;
+
+    UDateFormatStyle dateStyle = UDAT_NONE;
+    UDateFormatStyle timeStyle = UDAT_NONE;
+
+    if (args[1].isString()) {
+        JSLinearString* dateStyleStr = args[1].toString()->ensureLinear(cx);
+
+        if (StringEqualsAscii(dateStyleStr, "full")) {
+            dateStyle = UDAT_FULL;
+        } else if (StringEqualsAscii(dateStyleStr, "long")) {
+            dateStyle = UDAT_LONG;
+        } else if (StringEqualsAscii(dateStyleStr, "medium")) {
+            dateStyle = UDAT_MEDIUM;
+        } else if (StringEqualsAscii(dateStyleStr, "short")) {
+            dateStyle = UDAT_SHORT;
+        }
+    }
+
+    if (args[2].isString()) {
+        JSLinearString* timeStyleStr = args[2].toString()->ensureLinear(cx);
+
+        if (StringEqualsAscii(timeStyleStr, "full")) {
+            timeStyle = UDAT_FULL;
+        } else if (StringEqualsAscii(timeStyleStr, "long")) {
+            timeStyle = UDAT_LONG;
+        } else if (StringEqualsAscii(timeStyleStr, "medium")) {
+            timeStyle = UDAT_MEDIUM;
+        } else if (StringEqualsAscii(timeStyleStr, "short")) {
+            timeStyle = UDAT_SHORT;
+        }
+    }
+
+    UErrorCode status = U_ZERO_ERROR;
+    UDateFormat* df = udat_open(
+            timeStyle,
+            dateStyle,
+            icuLocale(locale.ptr()),
+            nullptr,
+            -1,
+            nullptr,
+            -1,
+            &status);
+    if (U_FAILURE(status)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        return false;
+    }
+    ScopedICUObject<UDateFormat, udat_close> toClose(df);
+
+    Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+    if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+        return false;
+
+    int32_t size = udat_toPattern(df, false, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE, &status);
+
+    if (status == U_BUFFER_OVERFLOW_ERROR) {
+        MOZ_ASSERT(size >= 0);
+        if (!chars.resize(size))
+            return false;
+        status = U_ZERO_ERROR;
+        udat_toPattern(df, false, Char16ToUChar(chars.begin()), size, &status);
+    }
+    if (U_FAILURE(status)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        return false;
+    }
+
+    MOZ_ASSERT(size >= 0);
+    JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size);
+    if (!str)
+        return false;
+    args.rval().setString(str);
+    return true;
+}
+
 /**
  * Returns a new UDateFormat with the locale and date-time formatting options
  * of the given DateTimeFormat.
  */
 static UDateFormat*
 NewUDateFormat(JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat)
 {
     RootedValue value(cx);
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -407,16 +407,26 @@ intl_defaultTimeZoneOffset(JSContext* cx
  * given locale.
  *
  * Usage: pattern = intl_patternForSkeleton(locale, skeleton)
  */
 extern MOZ_MUST_USE bool
 intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp);
 
 /**
+ * Return a pattern in the date-time format pattern language of Unicode
+ * Technical Standard 35, Unicode Locale Data Markup Language, for the
+ * best-fit date-time style for the given locale.
+ *
+ * Usage: pattern = intl_patternForStyle(locale, dateStyle, timeStyle)
+ */
+extern MOZ_MUST_USE bool
+intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp);
+
+/**
  * Returns a String value representing x (which must be a Number value)
  * according to the effective locale and the formatting options of the
  * given DateTimeFormat.
  *
  * Spec: ECMAScript Internationalization API Specification, 12.3.2.
  *
  * Usage: formatted = intl_FormatDateTime(dateTimeFormat, x, formatToParts)
  */
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -2227,17 +2227,35 @@ function resolveDateTimeFormatInternals(
 
     // Steps 15-17.
     internalProps.timeZone = lazyDateTimeFormatData.timeZone;
 
     // Step 18.
     var formatOpt = lazyDateTimeFormatData.formatOpt;
 
     // Steps 27-28, more or less - see comment after this function.
-    var pattern = toBestICUPattern(dataLocale, formatOpt);
+    var pattern;
+
+    if (lazyDateTimeFormatData.mozExtensions) {
+        var pattern;
+        if (lazyDateTimeFormatData.patternOption) {
+            pattern = lazyDateTimeFormatData.patternOption;
+        } else if (lazyDateTimeFormatData.dateStyle || lazyDateTimeFormatData.timeStyle) {
+          pattern = intl_patternForStyle(dataLocale,
+            lazyDateTimeFormatData.dateStyle, lazyDateTimeFormatData.timeStyle);
+
+          internalProps.dateStyle = lazyDateTimeFormatData.dateStyle;
+          internalProps.timeStyle = lazyDateTimeFormatData.timeStyle;
+        } else {
+          pattern = toBestICUPattern(dataLocale, formatOpt);
+        }
+        internalProps.mozExtensions = true;
+    } else {
+      pattern = toBestICUPattern(dataLocale, formatOpt);
+    }
 
     // Step 29.
     internalProps.pattern = pattern;
 
     // Step 30.
     internalProps.boundFormat = undefined;
 
     // The caller is responsible for associating |internalProps| with the right
@@ -2296,17 +2314,17 @@ function UnwrapDateTimeFormat(dtf, metho
  * This method is complicated a moderate bit by its implementing initialization
  * as a *lazy* concept.  Everything that must happen now, does -- but we defer
  * all the work we can until the object is actually used as a DateTimeFormat.
  * This later work occurs in |resolveDateTimeFormatInternals|; steps not noted
  * here occur there.
  *
  * Spec: ECMAScript Internationalization API Specification, 12.1.1.
  */
-function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options) {
+function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options, mozExtensions) {
     assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat called with non-Object");
     assert(IsDateTimeFormat(dateTimeFormat),
            "InitializeDateTimeFormat called with non-DateTimeFormat");
 
     // Steps 1-2 (These steps are no longer required and should be removed
     // from the spec; https://github.com/tc39/ecma402/issues/115).
 
     // Lazy DateTimeFormat data has the following structure:
@@ -2373,16 +2391,28 @@ function InitializeDateTimeFormat(dateTi
         tz = DefaultTimeZone();
     }
     lazyDateTimeFormatData.timeZone = tz;
 
     // Step 18.
     var formatOpt = new Record();
     lazyDateTimeFormatData.formatOpt = formatOpt;
 
+    if (mozExtensions) {
+        lazyDateTimeFormatData.mozExtensions = true;
+
+        let pattern = GetOption(options, "pattern", "string", undefined, undefined);
+        lazyDateTimeFormatData.patternOption = pattern;
+
+        let dateStyle = GetOption(options, "dateStyle", "string", ["full", "long", "medium", "short"], undefined);
+        lazyDateTimeFormatData.dateStyle = dateStyle;
+        let timeStyle = GetOption(options, "timeStyle", "string", ["full", "long", "medium", "short"], undefined);
+        lazyDateTimeFormatData.timeStyle = timeStyle;
+    }
+
     // Step 19.
     // 12.1, Table 4: Components of date and time formats.
     formatOpt.weekday = GetOption(options, "weekday", "string", ["narrow", "short", "long"],
                                   undefined);
     formatOpt.era = GetOption(options, "era", "string", ["narrow", "short", "long"], undefined);
     formatOpt.year = GetOption(options, "year", "string", ["2-digit", "numeric"], undefined);
     formatOpt.month = GetOption(options, "month", "string",
                                 ["2-digit", "numeric", "narrow", "short", "long"], undefined);
@@ -2478,18 +2508,18 @@ function InitializeDateTimeFormat(dateTi
 // have to be mapped to patterns before processing.
 //
 // The options of DateTimeFormat most closely correspond to ICU skeletons. This
 // implementation therefore, in the toBestICUPattern function, converts
 // DateTimeFormat options to ICU skeletons, and then lets ICU map skeletons to
 // actual ICU patterns. The pattern may not directly correspond to what the
 // skeleton requests, as the mapper (UDateTimePatternGenerator) is constrained
 // by the available locale data for the locale. The resulting ICU pattern is
-// kept as the DateTimeFormat's [[pattern]] internal property and passed to ICU
-// in the format method.
+// kept as the DateTimeFormat's [[pattern]] internal property and
+// passed to ICU in the format method.
 //
 // An ICU pattern represents the information of the following DateTimeFormat
 // internal properties described in the specification, which therefore don't
 // exist separately in the implementation:
 // - [[weekday]], [[era]], [[year]], [[month]], [[day]], [[hour]], [[minute]],
 //   [[second]], [[timeZoneName]]
 // - [[hour12]]
 // - [[hourNo0]]
@@ -2887,18 +2917,30 @@ function Intl_DateTimeFormat_resolvedOpt
     var dtf = UnwrapDateTimeFormat(this, "resolvedOptions");
 
     var internals = getDateTimeFormatInternals(dtf);
 
     var result = {
         locale: internals.locale,
         calendar: internals.calendar,
         numberingSystem: internals.numberingSystem,
-        timeZone: internals.timeZone
+        timeZone: internals.timeZone,
     };
+
+    if (internals.mozExtensions) {
+        result.pattern = internals.pattern;
+
+        if (internals.dateStyle) {
+          result.dateStyle = internals.dateStyle;
+        }
+        if (internals.timeStyle) {
+          result.timeStyle = internals.timeStyle;
+        }
+    }
+
     resolveICUPattern(internals.pattern, result);
     return result;
 }
 
 
 // Table mapping ICU pattern characters back to the corresponding date-time
 // components of DateTimeFormat. See
 // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2860,16 +2860,21 @@ extern JS_FRIEND_API(JSObject*)
 ToWindowIfWindowProxy(JSObject* obj);
 
 // Create and add the Intl.PluralRules constructor function to the provided
 // object.  This function throws if called more than once per realm/global
 // object.
 extern bool
 AddPluralRulesConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
 
+// Create and add the Intl.DateTimeFormat constructor function to the provided
+// object.
+extern bool
+AddDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
+
 class MOZ_STACK_CLASS JS_FRIEND_API(AutoAssertNoContentJS)
 {
   public:
     explicit AutoAssertNoContentJS(JSContext* cx);
     ~AutoAssertNoContentJS();
 
   private:
     JSContext* context_;
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -908,16 +908,19 @@ AddIntlExtras(JSContext* cx, unsigned ar
     };
 
     if (!JS_DefineFunctions(cx, intl, funcs))
         return false;
 
     if (!js::AddPluralRulesConstructor(cx, intl))
         return false;
 
+    if (!js::AddDateTimeFormatConstructor(cx, intl))
+        return false;
+
     args.rval().setUndefined();
     return true;
 }
 #endif // ENABLE_INTL_API
 
 static bool
 EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
              int lineno, bool compileOnly)
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/mozExtensions.js
@@ -0,0 +1,58 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the format function with a diverse set of locales and options.
+// Always use UTC to avoid dependencies on test environment.
+
+let mozIntl = {};
+addIntlExtras(mozIntl);
+
+// Pattern
+
+var dtf = new Intl.DateTimeFormat("en-US", {pattern: "HH:mm MM/dd/YYYY"});
+var mozDtf = new mozIntl.DateTimeFormat("en-US", {pattern: "HH:mm MM/dd/YYYY"});
+
+assertEq(dtf.resolvedOptions().hasOwnProperty('pattern'), false);
+assertEq(mozDtf.resolvedOptions().pattern, "HH:mm MM/dd/YYYY");
+
+// Date style
+
+var dtf = new Intl.DateTimeFormat("en-US", {dateStyle: 'long'});
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('dateStyle'), false);
+
+var mozDtf = new mozIntl.DateTimeFormat("en-US", {dateStyle: 'long'});
+assertEq(mozDtf.resolvedOptions().dateStyle, 'long');
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('year'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('month'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('day'), true);
+
+// Time style
+
+var dtf = new Intl.DateTimeFormat("en-US", {timeStyle: 'long'});
+assertEq(dtf.resolvedOptions().hasOwnProperty('dateStyle'), false);
+
+var mozDtf = new mozIntl.DateTimeFormat("en-US", {timeStyle: 'long'});
+assertEq(mozDtf.resolvedOptions().timeStyle, 'long');
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('hour'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('minute'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('second'), true);
+
+// Date/Time style
+
+var dtf = new Intl.DateTimeFormat("en-US", {timeStyle: 'medium', dateStyle: 'medium'});
+assertEq(dtf.resolvedOptions().hasOwnProperty('dateStyle'), false);
+assertEq(dtf.resolvedOptions().hasOwnProperty('timeStyle'), false);
+
+var mozDtf = new mozIntl.DateTimeFormat("en-US", {dateStyle: 'medium', timeStyle: 'medium'});
+assertEq(mozDtf.resolvedOptions().timeStyle, 'medium');
+assertEq(mozDtf.resolvedOptions().dateStyle, 'medium');
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('hour'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('minute'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('second'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('year'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('month'), true);
+assertEq(mozDtf.resolvedOptions().hasOwnProperty('day'), true);
+
+reportCompare(0, 0, 'ok');
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2490,16 +2490,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
     JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
     JS_FN("intl_ComputeDisplayNames", intl_ComputeDisplayNames, 3,0),
     JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
     JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
     JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
     JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
     JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
+    JS_FN("intl_patternForStyle", intl_patternForStyle, 3,0),
     JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
     JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
     JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
 
     JS_INLINABLE_FN("IsCollator",
                     intrinsic_IsInstanceOfBuiltin<CollatorObject>, 1,0,
                     IntlIsCollator),
     JS_INLINABLE_FN("IsDateTimeFormat",