Bug 1339650 - Introduce mozIntl.DateTimeFormat. r?jfkthame draft
authorZibi Braniecki <gandalf@mozilla.com>
Thu, 02 Mar 2017 16:39:17 -0800
changeset 501775 3ab1064782c020c55760f11ec3299c10375f229b
parent 501569 05bfa2831c0ba4a26fa72328ffe6a99aba9c356a
child 550008 7d3e5573184e42ee5cf91e058735ab7982ce09f2
push id50124
push userzbraniecki@mozilla.com
push dateMon, 20 Mar 2017 23:59:12 +0000
reviewersjfkthame
bugs1339650
milestone55.0a1
Bug 1339650 - Introduce mozIntl.DateTimeFormat. r?jfkthame MozReview-Commit-ID: 1jnit4IlDN6
intl/locale/LocaleService.cpp
intl/locale/LocaleService.h
intl/locale/OSPreferences.cpp
intl/locale/mac/OSPreferences_mac.cpp
intl/locale/windows/OSPreferences_win.cpp
toolkit/components/mozintl/MozIntlHelper.cpp
toolkit/components/mozintl/mozIMozIntl.idl
toolkit/components/mozintl/mozIMozIntlHelper.idl
toolkit/components/mozintl/mozIntl.js
toolkit/components/mozintl/test/test_mozintl.js
toolkit/components/mozintl/test/test_mozintlhelper.js
--- a/intl/locale/LocaleService.cpp
+++ b/intl/locale/LocaleService.cpp
@@ -361,16 +361,30 @@ LocaleService::Observe(nsISupports *aSub
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
       obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
     }
   }
   return NS_OK;
 }
 
+bool
+LocaleService::LanguagesMatch(const nsCString& aRequested,
+		              const nsCString& aAvailable)
+{
+  nsTArray<nsCString> requestedLocales;
+  requestedLocales.AppendElement(aRequested);
+  nsTArray<nsCString> availableLocales;
+  availableLocales.AppendElement(aAvailable);
+  nsTArray<nsCString> supportedLocales;
+
+  FilterMatches(requestedLocales, availableLocales, LangNegStrategy::Lookup, supportedLocales);
+  return !supportedLocales.IsEmpty();
+}
+
 /**
  * mozILocaleService methods
  */
 
 static char**
 CreateOutArray(const nsTArray<nsCString>& aArray)
 {
   uint32_t n = aArray.Length();
--- a/intl/locale/LocaleService.h
+++ b/intl/locale/LocaleService.h
@@ -142,16 +142,19 @@ public:
    * (See mozILocaleService.idl for a JS-callable version of this.)
    */
   bool NegotiateLanguages(const nsTArray<nsCString>& aRequested,
                           const nsTArray<nsCString>& aAvailable,
                           const nsACString& aDefaultLocale,
                           LangNegStrategy aLangNegStrategy,
                           nsTArray<nsCString>& aRetVal);
 
+  bool LanguagesMatch(const nsCString& aRequested,
+		      const nsCString& aAvailable);
+
 private:
   /**
    * Locale object, a BCP47-style tag decomposed into subtags for
    * matching purposes.
    *
    * If constructed with aRange = true, any missing subtags will be
    * set to "*".
    */
--- a/intl/locale/OSPreferences.cpp
+++ b/intl/locale/OSPreferences.cpp
@@ -145,19 +145,26 @@ OSPreferences::GetDateTimePatternForStyl
     case DateTimeFormatStyle::Invalid:
       dateStyle = UDAT_NONE;
       break;
   }
 
   const int32_t kPatternMax = 160;
   UChar pattern[kPatternMax];
 
+  nsAutoCString locale;
+  if (aLocale.IsEmpty()) {
+    LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale);
+  } else {
+    locale.Assign(aLocale);
+  }
+
   UErrorCode status = U_ZERO_ERROR;
   UDateFormat* df = udat_open(timeStyle, dateStyle,
-                              PromiseFlatCString(aLocale).get(),
+                              locale.get(),
                               nullptr, -1, nullptr, -1, &status);
   if (U_FAILURE(status)) {
     return false;
   }
 
   int32_t patsize = udat_toPattern(df, false, pattern, kPatternMax, &status);
   udat_close(df);
   if (U_FAILURE(status)) {
--- a/intl/locale/mac/OSPreferences_mac.cpp
+++ b/intl/locale/mac/OSPreferences_mac.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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/. */
 
 #include "OSPreferences.h"
+#include "mozilla/intl/LocaleService.h"
 #include <Carbon/Carbon.h>
 
 using namespace mozilla::intl;
 
 bool
 OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList)
 {
   MOZ_ASSERT(aLocaleList.IsEmpty());
@@ -62,23 +63,36 @@ ToCFDateFormatterStyle(OSPreferences::Da
 // Given an 8-bit Gecko string, create a corresponding CFLocale;
 // if aLocale is empty, returns a copy of the system's current locale.
 // May return null on failure.
 // Follows Core Foundation's Create rule, so the caller is responsible to
 // release the returned reference.
 static CFLocaleRef
 CreateCFLocaleFor(const nsACString& aLocale)
 {
+  nsCString reqLocale;
+  nsCString systemLocale;
+
+  OSPreferences::GetInstance()->GetSystemLocale(systemLocale);
+
   if (aLocale.IsEmpty()) {
-    return CFLocaleCopyCurrent();
+    LocaleService::GetInstance()->GetAppLocale(reqLocale);
+  } else {
+    reqLocale.Assign(aLocale);
   }
+
+  bool match = LocaleService::GetInstance()->LanguagesMatch(reqLocale, systemLocale);
+  if (match) {
+    return ::CFLocaleCopyCurrent();
+  }
+
   CFStringRef identifier =
     CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
-                                  (const uint8_t*)aLocale.BeginReading(),
-                                  aLocale.Length(), kCFStringEncodingASCII,
+                                  (const uint8_t*)reqLocale.BeginReading(),
+                                  reqLocale.Length(), kCFStringEncodingASCII,
                                   false, kCFAllocatorNull);
   if (!identifier) {
     return nullptr;
   }
   CFLocaleRef locale = CFLocaleCreate(kCFAllocatorDefault, identifier);
   CFRelease(identifier);
   return locale;
 }
--- a/intl/locale/windows/OSPreferences_win.cpp
+++ b/intl/locale/windows/OSPreferences_win.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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/. */
 
 #include "OSPreferences.h"
+#include "mozilla/intl/LocaleService.h"
 #include "nsWin32Locale.h"
 
 using namespace mozilla::intl;
 
 bool
 OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList)
 {
   MOZ_ASSERT(aLocaleList.IsEmpty());
@@ -65,16 +66,40 @@ ToTimeLCType(OSPreferences::DateTimeForm
       return LOCALE_STIMEFORMAT;
     case OSPreferences::DateTimeFormatStyle::Invalid:
     default:
       MOZ_ASSERT_UNREACHABLE("invalid time format");
       return LOCALE_STIMEFORMAT;
   }
 }
 
+LPWSTR CreateLocaleFor(const nsACString& aLocale)
+{
+  nsCString reqLocale;
+  nsCString systemLocale;
+  OSPreferences::GetInstance()->GetSystemLocale(systemLocale);
+
+  if (aLocale.IsEmpty()) {
+    LocaleService::GetInstance()->GetAppLocale(reqLocale);
+  } else {
+    reqLocale.Assign(aLocale);
+  }
+
+  bool match = LocaleService::GetInstance()->LanguagesMatch(reqLocale, systemLocale);
+
+  if (match) {
+    return LOCALE_NAME_USER_DEFAULT;
+  }
+
+  nsAutoString localeNameBuffer;
+  localeNameBuffer.AppendASCII(reqLocale.BeginReading(), reqLocale.Length());
+  LPWSTR localeName = (LPWSTR)localeNameBuffer.BeginReading();
+  return localeName;
+}
+
 /**
  * Windows API includes regional preferences from the user only
  * if we pass empty locale string or if the locale string matches
  * the current locale.
  *
  * Since Windows API only allows us to retrieve two options - short/long
  * we map it to our four options as:
  *
@@ -87,36 +112,32 @@ ToTimeLCType(OSPreferences::DateTimeForm
  * for combined date/time string, since Windows API does not provide an
  * option for this.
  */
 bool
 OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
                                    DateTimeFormatStyle aTimeStyle,
                                    const nsACString& aLocale, nsAString& aRetVal)
 {
-  LPWSTR localeName = LOCALE_NAME_USER_DEFAULT;
-  nsAutoString localeNameBuffer;
-  if (!aLocale.IsEmpty()) {
-    localeNameBuffer.AppendASCII(aLocale.BeginReading(), aLocale.Length());
-    localeName = (LPWSTR)localeNameBuffer.BeginReading();
-  }
+  LPWSTR localeName = CreateLocaleFor(aLocale);
 
   bool isDate = aDateStyle != DateTimeFormatStyle::None &&
                 aDateStyle != DateTimeFormatStyle::Invalid;
   bool isTime = aTimeStyle != DateTimeFormatStyle::None &&
                 aTimeStyle != DateTimeFormatStyle::Invalid;
 
   // If both date and time are wanted, we'll initially read them into a
   // local string, and then insert them into the overall date+time pattern;
   // but if only one is needed we'll work directly with the return value.
   // Set 'str' to point to the string we will use to retrieve patterns
   // from Windows.
   nsAutoString tmpStr;
   nsAString* str;
   if (isDate && isTime) {
+    //XXX: We should get it for localeName
     if (!GetDateTimeConnectorPattern(aLocale, aRetVal)) {
       NS_WARNING("failed to get date/time connector");
       aRetVal.AssignLiteral(u"{1} {0}");
     }
     str = &tmpStr;
   } else if (isDate || isTime) {
     str = &aRetVal;
   } else {
--- a/toolkit/components/mozintl/MozIntlHelper.cpp
+++ b/toolkit/components/mozintl/MozIntlHelper.cpp
@@ -78,16 +78,37 @@ MozIntlHelper::AddPluralRulesConstructor
   if (!js::AddPluralRulesConstructor(cx, realIntlObj)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+MozIntlHelper::AddDateTimeFormatConstructor(JS::Handle<JS::Value> val, JSContext* cx)
+{
+  if (!val.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JS::Rooted<JSObject*> realIntlObj(cx, js::CheckedUnwrap(&val.toObject()));
+  if (!realIntlObj) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JSAutoCompartment ac(cx, realIntlObj);
+
+  if (!js::AddMozDateTimeFormatConstructor(cx, realIntlObj)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 MozIntlHelper::AddGetLocaleInfo(JS::Handle<JS::Value> val, JSContext* cx)
 {
   static const JSFunctionSpec funcs[] = {
     JS_SELF_HOSTED_FN("getLocaleInfo", "Intl_getLocaleInfo", 1, 0),
     JS_FS_END
   };
 
   return AddFunctions(cx, val, funcs);
--- a/toolkit/components/mozintl/mozIMozIntl.idl
+++ b/toolkit/components/mozintl/mozIMozIntl.idl
@@ -37,9 +37,10 @@
 [scriptable, uuid(7f63279a-1a29-4ae6-9e7a-dc9684a23530)]
 interface mozIMozIntl : nsISupports
 {
   jsval getCalendarInfo([optional] in jsval locales);
   jsval getDisplayNames([optional] in jsval locales, [optional] in jsval options);
   jsval getLocaleInfo([optional] in jsval locales);
 
   jsval createPluralRules([optional] in jsval locales, [optional] in jsval options);
+  jsval createDateTimeFormat([optional] in jsval locales, [optional] in jsval options);
 };
--- a/toolkit/components/mozintl/mozIMozIntlHelper.idl
+++ b/toolkit/components/mozintl/mozIMozIntlHelper.idl
@@ -21,9 +21,10 @@ interface mozIMozIntlHelper : nsISupport
   [implicit_jscontext] void addGetLocaleInfo(in jsval intlObject);
 
   /**
    * Adds a PluralRules constructor to the given object.  This function may only
    * be called once within a realm/global object: calling it multiple times will
    * throw.
    */
   [implicit_jscontext] void addPluralRulesConstructor(in jsval intlObject);
+  [implicit_jscontext] void addDateTimeFormatConstructor(in jsval intlObject);
 };
--- a/toolkit/components/mozintl/mozIntl.js
+++ b/toolkit/components/mozintl/mozIntl.js
@@ -6,29 +6,56 @@ Components.utils.import("resource://gre/
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 const mozIntlHelper =
   Cc["@mozilla.org/mozintlhelper;1"].getService(Ci.mozIMozIntlHelper);
 const localeSvc =
   Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService);
+const osPrefs =
+  Cc["@mozilla.org/intl/ospreferences;1"].getService(Ci.mozIOSPreferences);
 
 /**
  * This helper function retrives currently used app locales, allowing
  * all mozIntl APIs to use the current app locales unless called with
  * explicitly listed locales.
  */
 function getLocales(locales) {
   if (!locales) {
     return localeSvc.getAppLocalesAsBCP47();
   }
   return locales;
 }
 
+function getLocale(locales) {
+  if (!locales) {
+    return localeSvc.getAppLocale();
+  }
+  if (Array.isArray(locales)) {
+    return [0];
+  }
+  return locales;
+}
+
+function getDateTimePatternStyle(option) {
+  switch (option) {
+    case 'full':
+      return osPrefs.dateTimeFormatStyleFull;
+    case 'long':
+      return osPrefs.dateTimeFormatStyleLong;
+    case 'medium':
+      return osPrefs.dateTimeFormatStyleMedium;
+    case 'short':
+      return osPrefs.dateTimeFormatStyleShort;
+    default:
+      return osPrefs.dateTimeFormatStyleNone;
+  }
+}
+
 class MozIntl {
   constructor() {
     this._cache = {};
   }
 
   getCalendarInfo(locales, ...args) {
     if (!this._cache.hasOwnProperty("getCalendarInfo")) {
       mozIntlHelper.addGetCalendarInfo(this._cache);
@@ -55,15 +82,38 @@ class MozIntl {
 
   createPluralRules(locales, ...args) {
     if (!this._cache.hasOwnProperty("PluralRules")) {
       mozIntlHelper.addPluralRulesConstructor(this._cache);
     }
 
     return new this._cache.PluralRules(getLocales(locales), ...args);
   }
+
+  createDateTimeFormat(locales, options, ...args) {
+    if (!this._cache.hasOwnProperty("DateTimeFormat")) {
+      mozIntlHelper.addDateTimeFormatConstructor(this._cache);
+    }
+
+    let resolvedLocales =
+      this._cache.DateTimeFormat.supportedLocalesOf(getLocales(locales));
+
+    if (options) {
+      if (options.dateStyle || options.timeStyle) {
+        options.pattern = osPrefs.getDateTimePattern(
+          getDateTimePatternStyle(options.dateStyle),
+          getDateTimePatternStyle(options.timeStyle),
+          resolvedLocales[0]);
+      } else {
+        // make sure that user doesn't pass a pattern explicitly
+        options.pattern = undefined;
+      }
+    }
+
+    return new this._cache.DateTimeFormat(resolvedLocales, options, ...args);
+  }
 }
 
 MozIntl.prototype.classID = Components.ID("{35ec195a-e8d0-4300-83af-c8a2cc84b4a3}");
 MozIntl.prototype.QueryInterface = XPCOMUtils.generateQI([Ci.mozIMozIntl, Ci.nsISupports]);
 
 var components = [MozIntl];
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/toolkit/components/mozintl/test/test_mozintl.js
+++ b/toolkit/components/mozintl/test/test_mozintl.js
@@ -11,17 +11,19 @@ function run_test() {
   ok(true);
 }
 
 function test_methods_presence(mozIntl) {
   equal(mozIntl.getCalendarInfo instanceof Function, true);
   equal(mozIntl.getDisplayNames instanceof Function, true);
   equal(mozIntl.getLocaleInfo instanceof Function, true);
   equal(mozIntl.createPluralRules instanceof Function, true);
+  equal(mozIntl.createDateTimeFormat instanceof Function, true);
 }
 
 function test_methods_calling(mozIntl) {
   mozIntl.getCalendarInfo("pl");
   mozIntl.getDisplayNames("ar");
   mozIntl.getLocaleInfo("de");
   mozIntl.createPluralRules("fr");
+  mozIntl.createDateTimeFormat("fr");
   ok(true);
 }
--- a/toolkit/components/mozintl/test/test_mozintlhelper.js
+++ b/toolkit/components/mozintl/test/test_mozintlhelper.js
@@ -31,20 +31,28 @@ function test_cross_global(miHelper) {
   equal(waivedX.getCalendarInfo() instanceof Object, false);
   equal(waivedX.getCalendarInfo() instanceof global.Object, true);
 }
 
 function test_methods_presence(miHelper) {
   equal(miHelper.addGetCalendarInfo instanceof Function, true);
   equal(miHelper.addGetDisplayNames instanceof Function, true);
   equal(miHelper.addGetLocaleInfo instanceof Function, true);
+  equal(miHelper.addPluralRulesConstructor instanceof Function, true);
+  equal(miHelper.addDateTimeFormatConstructor instanceof Function, true);
 
   let x = {};
 
   miHelper.addGetCalendarInfo(x);
   equal(x.getCalendarInfo instanceof Function, true);
 
   miHelper.addGetDisplayNames(x);
   equal(x.getDisplayNames instanceof Function, true);
 
   miHelper.addGetLocaleInfo(x);
   equal(x.getLocaleInfo instanceof Function, true);
+
+  miHelper.addPluralRulesConstructor(x);
+  equal(x.PluralRules instanceof Function, true);
+
+  miHelper.addDateTimeFormatConstructor(x);
+  equal(x.DateTimeFormat instanceof Function, true);
 }