Bug 1308329 - Extend OSPreferences API to cover date/time styles. r=jfkthame draft
authorZibi Braniecki <gandalf@mozilla.com>
Wed, 08 Feb 2017 17:17:51 -0800
changeset 491621 6e09bb36c1d4fe89ed22c2fc5ef8de3ff8ed484d
parent 490976 e150eaff1f83e4e4a97d1e30c57d233859efe9cb
child 491632 3960f583a626f5f00a41d782b65a6b30772c63e8
child 491755 f6d381b2ae680c5ecbc47bbe5a08120dbce19c96
child 491757 dd0b6294394e5c568655071159c6ed5f4a5e9d77
child 491764 aad9cde3db2eb39bd00bf0ff32dd123d9926884f
child 491948 94da0a775b9c613269f7665bd1bee530a5fd94b5
child 492092 9753dbeea2e11c7fde550df1a20b1c3d1b2063fe
push id47353
push userzbraniecki@mozilla.com
push dateWed, 01 Mar 2017 23:57:16 +0000
reviewersjfkthame
bugs1308329
milestone54.0a1
Bug 1308329 - Extend OSPreferences API to cover date/time styles. r=jfkthame MozReview-Commit-ID: HnuWfS8UEDH
intl/build/nsI18nModule.cpp
intl/locale/OSPreferences.cpp
intl/locale/OSPreferences.h
intl/locale/gtk/OSPreferences_gtk.cpp
intl/locale/gtk/moz.build
intl/locale/mac/OSPreferences_mac.cpp
intl/locale/moz.build
intl/locale/mozIOSPreferences.idl
intl/locale/nsLocaleConstructors.h
intl/locale/tests/gtest/TestOSPreferences.cpp
intl/locale/tests/unit/test_osPreferences.js
intl/locale/tests/unit/xpcshell.ini
intl/locale/unix/OSPreferences_unix.cpp
intl/locale/unix/moz.build
intl/locale/windows/OSPreferences_win.cpp
--- a/intl/build/nsI18nModule.cpp
+++ b/intl/build/nsI18nModule.cpp
@@ -40,16 +40,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSt
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsCaseConversionImp2)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsCategoryImp,
                                          nsCategoryImp::GetInstance)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsEntityConverter)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSaveAsCharset)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeNormalizer)
 
 NS_DEFINE_NAMED_CID(MOZ_LOCALESERVICE_CID);
+NS_DEFINE_NAMED_CID(MOZ_OSPREFERENCES_CID);
 NS_DEFINE_NAMED_CID(NS_LBRK_CID);
 NS_DEFINE_NAMED_CID(NS_WBRK_CID);
 NS_DEFINE_NAMED_CID(NS_SEMANTICUNITSCANNER_CID);
 NS_DEFINE_NAMED_CID(NS_UNICHARUTIL_CID);
 NS_DEFINE_NAMED_CID(NS_UNICHARCATEGORY_CID);
 NS_DEFINE_NAMED_CID(NS_ENTITYCONVERTER_CID);
 NS_DEFINE_NAMED_CID(NS_SAVEASCHARSET_CID);
 NS_DEFINE_NAMED_CID(NS_UNICODE_NORMALIZER_CID);
@@ -67,16 +68,17 @@ NS_DEFINE_NAMED_CID(NS_COLLATION_CID);
 NS_DEFINE_NAMED_CID(NS_COLLATION_CID);
 #endif
 #ifdef USE_MAC_LOCALE
 NS_DEFINE_NAMED_CID(NS_COLLATION_CID);
 #endif
 
 static const mozilla::Module::CIDEntry kIntlCIDs[] = {
     { &kMOZ_LOCALESERVICE_CID, false, nullptr, mozilla::intl::LocaleServiceConstructor },
+    { &kMOZ_OSPREFERENCES_CID, false, nullptr, mozilla::intl::OSPreferencesConstructor },
     { &kNS_LBRK_CID, false, nullptr, nsJISx4051LineBreakerConstructor },
     { &kNS_WBRK_CID, false, nullptr, nsSampleWordBreakerConstructor },
     { &kNS_SEMANTICUNITSCANNER_CID, false, nullptr, nsSemanticUnitScannerConstructor },
     { &kNS_UNICHARUTIL_CID, false, nullptr, nsCaseConversionImp2Constructor },
     { &kNS_UNICHARCATEGORY_CID, false, nullptr, nsCategoryImpConstructor },
     { &kNS_ENTITYCONVERTER_CID, false, nullptr, nsEntityConverterConstructor },
     { &kNS_SAVEASCHARSET_CID, false, nullptr, nsSaveAsCharsetConstructor },
     { &kNS_UNICODE_NORMALIZER_CID, false, nullptr, nsUnicodeNormalizerConstructor },
@@ -96,16 +98,17 @@ static const mozilla::Module::CIDEntry k
 #ifdef USE_MAC_LOCALE
     { &kNS_COLLATION_CID, false, nullptr, nsCollationMacUCConstructor },
 #endif
     { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kIntlContracts[] = {
     { MOZ_LOCALESERVICE_CONTRACTID, &kMOZ_LOCALESERVICE_CID },
+    { MOZ_OSPREFERENCES_CONTRACTID, &kMOZ_OSPREFERENCES_CID },
     { NS_LBRK_CONTRACTID, &kNS_LBRK_CID },
     { NS_WBRK_CONTRACTID, &kNS_WBRK_CID },
     { NS_SEMANTICUNITSCANNER_CONTRACTID, &kNS_SEMANTICUNITSCANNER_CID },
     { NS_UNICHARUTIL_CONTRACTID, &kNS_UNICHARUTIL_CID },
     { NS_UNICHARCATEGORY_CONTRACTID, &kNS_UNICHARCATEGORY_CID },
     { NS_ENTITYCONVERTER_CONTRACTID, &kNS_ENTITYCONVERTER_CID },
     { NS_SAVEASCHARSET_CONTRACTID, &kNS_SAVEASCHARSET_CID },
     { NS_UNICODE_NORMALIZER_CONTRACTID, &kNS_UNICODE_NORMALIZER_CID },
--- a/intl/locale/OSPreferences.cpp
+++ b/intl/locale/OSPreferences.cpp
@@ -5,21 +5,28 @@
 
 /**
  * This is a shared part of the OSPreferences API implementation.
  * It defines helper methods and public methods that are calling
  * platform-specific private methods.
  */
 
 #include "OSPreferences.h"
+
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "unicode/udat.h"
+#include "unicode/udatpg.h"
 
 using namespace mozilla::intl;
 
-mozilla::StaticAutoPtr<OSPreferences> OSPreferences::sInstance;
+NS_IMPL_ISUPPORTS(OSPreferences, mozIOSPreferences)
+
+mozilla::StaticRefPtr<OSPreferences> OSPreferences::sInstance;
 
 OSPreferences*
 OSPreferences::GetInstance()
 {
   if (!sInstance) {
     sInstance = new OSPreferences();
     ClearOnShutdown(&sInstance);
   }
@@ -32,16 +39,31 @@ OSPreferences::GetSystemLocales(nsTArray
   bool status = true;
   if (mSystemLocales.IsEmpty()) {
     status = ReadSystemLocales(mSystemLocales);
   }
   aRetVal = mSystemLocales;
   return status;
 }
 
+void
+OSPreferences::Refresh()
+{
+  nsTArray<nsCString> newLocales;
+  ReadSystemLocales(newLocales);
+
+  if (mSystemLocales != newLocales) {
+    mSystemLocales = Move(newLocales);
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->NotifyObservers(nullptr, "intl:system-locales-changed", nullptr);
+    }
+  }
+}
+
 /**
  * This method should be called by every method of OSPreferences that
  * retrieves a locale id from external source.
  *
  * It attempts to retrieve as much of the locale ID as possible, cutting
  * out bits that are not understood (non-strict behavior of ICU).
  *
  * It returns true if the canonicalization was successful.
@@ -58,8 +80,264 @@ OSPreferences::CanonicalizeLanguageTag(n
 
   if (U_FAILURE(status)) {
     return false;
   }
 
   aLoc.Assign(langTag, langTagLen);
   return true;
 }
+
+/**
+ * This method retrieves from ICU the best pattern for a given date/time style.
+ */
+bool
+OSPreferences::GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle,
+                                          DateTimeFormatStyle aTimeStyle,
+                                          const nsACString& aLocale,
+                                          nsAString& aRetVal)
+{
+  UDateFormatStyle timeStyle = UDAT_NONE;
+  UDateFormatStyle dateStyle = UDAT_NONE;
+
+  switch (aTimeStyle) {
+    case DateTimeFormatStyle::None:
+      timeStyle = UDAT_NONE;
+      break;
+    case DateTimeFormatStyle::Short:
+      timeStyle = UDAT_SHORT;
+      break;
+    case DateTimeFormatStyle::Medium:
+      timeStyle = UDAT_MEDIUM;
+      break;
+    case DateTimeFormatStyle::Long:
+      timeStyle = UDAT_LONG;
+      break;
+    case DateTimeFormatStyle::Full:
+      timeStyle = UDAT_FULL;
+      break;
+    case DateTimeFormatStyle::Invalid:
+      timeStyle = UDAT_NONE;
+      break;
+  }
+
+  switch (aDateStyle) {
+    case DateTimeFormatStyle::None:
+      dateStyle = UDAT_NONE;
+      break;
+    case DateTimeFormatStyle::Short:
+      dateStyle = UDAT_SHORT;
+      break;
+    case DateTimeFormatStyle::Medium:
+      dateStyle = UDAT_MEDIUM;
+      break;
+    case DateTimeFormatStyle::Long:
+      dateStyle = UDAT_LONG;
+      break;
+    case DateTimeFormatStyle::Full:
+      dateStyle = UDAT_FULL;
+      break;
+    case DateTimeFormatStyle::Invalid:
+      dateStyle = UDAT_NONE;
+      break;
+  }
+
+  const int32_t kPatternMax = 160;
+  UChar pattern[kPatternMax];
+
+  UErrorCode status = U_ZERO_ERROR;
+  UDateFormat* df = udat_open(timeStyle, dateStyle,
+                              PromiseFlatCString(aLocale).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)) {
+    return false;
+  }
+  aRetVal.Assign((const char16_t*)pattern, patsize);
+  return true;
+}
+
+
+/**
+ * This method retrieves from ICU the best skeleton for a given date/time style.
+ *
+ * This is useful for cases where an OS does not provide its own patterns,
+ * but provide ability to customize the skeleton, like alter hourCycle setting.
+ *
+ * The returned value is a skeleton that matches the styles.
+ */
+bool
+OSPreferences::GetDateTimeSkeletonForStyle(DateTimeFormatStyle aDateStyle,
+                                           DateTimeFormatStyle aTimeStyle,
+                                           const nsACString& aLocale,
+                                           nsAString& aRetVal)
+{
+  nsAutoString pattern;
+  if (!GetDateTimePatternForStyle(aDateStyle, aTimeStyle, aLocale, pattern)) {
+    return false;
+  }
+
+  const int32_t kSkeletonMax = 160;
+  UChar skeleton[kSkeletonMax];
+
+  UErrorCode status = U_ZERO_ERROR;
+  int32_t skelsize = udatpg_getSkeleton(
+    nullptr, (const UChar*)pattern.BeginReading(), pattern.Length(),
+    skeleton, kSkeletonMax, &status
+  );
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  aRetVal.Assign((const char16_t*)skeleton, skelsize);
+  return true;
+}
+
+/**
+ * This function is a counterpart to GetDateTimeSkeletonForStyle.
+ *
+ * It takes a skeleton and returns the best available pattern for a given locale
+ * that represents the provided skeleton.
+ *
+ * For example:
+ * "Hm" skeleton for "en-US" will return "H:m"
+ */
+bool
+OSPreferences::GetPatternForSkeleton(const nsAString& aSkeleton,
+                                     const nsACString& aLocale,
+                                     nsAString& aRetVal)
+{
+  UErrorCode status = U_ZERO_ERROR;
+  UDateTimePatternGenerator* pg = udatpg_open(PromiseFlatCString(aLocale).get(), &status);
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  int32_t len =
+    udatpg_getBestPattern(pg, (const UChar*)aSkeleton.BeginReading(),
+                          aSkeleton.Length(), nullptr, 0, &status);
+  if (status == U_BUFFER_OVERFLOW_ERROR) { // expected
+    aRetVal.SetLength(len);
+    status = U_ZERO_ERROR;
+    udatpg_getBestPattern(pg, (const UChar*)aSkeleton.BeginReading(),
+                          aSkeleton.Length(), (UChar*)aRetVal.BeginWriting(),
+                          len, &status);
+  }
+
+  udatpg_close(pg);
+
+  return U_SUCCESS(status);
+}
+
+/**
+ * This function returns a pattern that should be used to join date and time
+ * patterns into a single date/time pattern string.
+ *
+ * It's useful for OSes that do not provide an API to retrieve such combined
+ * pattern.
+ *
+ * An example output is "{1}, {0}".
+ */
+bool
+OSPreferences::GetDateTimeConnectorPattern(const nsACString& aLocale,
+                                           nsAString& aRetVal)
+{
+  UErrorCode status = U_ZERO_ERROR;
+  UDateTimePatternGenerator* pg = udatpg_open(PromiseFlatCString(aLocale).get(), &status);
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  int32_t resultSize;
+  const UChar* value = udatpg_getDateTimeFormat(pg, &resultSize);
+  MOZ_ASSERT(resultSize >= 0);
+
+  aRetVal.Assign((char16_t*)value, resultSize);
+  return true;
+}
+
+/**
+ * mozIOSPreferences methods
+ */
+NS_IMETHODIMP
+OSPreferences::GetSystemLocales(uint32_t* aCount, char*** aOutArray)
+{
+  if (mSystemLocales.IsEmpty()) {
+    ReadSystemLocales(mSystemLocales);
+  }
+
+  *aCount = mSystemLocales.Length();
+  *aOutArray = static_cast<char**>(moz_xmalloc(*aCount * sizeof(char*)));
+
+  for (uint32_t i = 0; i < *aCount; i++) {
+    (*aOutArray)[i] = moz_xstrdup(mSystemLocales[i].get());
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+OSPreferences::GetSystemLocale(nsACString& aRetVal)
+{
+  if (mSystemLocales.IsEmpty()) {
+    ReadSystemLocales(mSystemLocales);
+  }
+
+  if (!mSystemLocales.IsEmpty()) {
+    aRetVal = mSystemLocales[0];
+  }
+  return NS_OK;
+}
+
+static OSPreferences::DateTimeFormatStyle
+ToDateTimeFormatStyle(int32_t aTimeFormat)
+{
+  switch (aTimeFormat) {
+    // See mozIOSPreferences.idl for the integer values here.
+    case 0:
+      return OSPreferences::DateTimeFormatStyle::None;
+    case 1:
+      return OSPreferences::DateTimeFormatStyle::Short;
+    case 2:
+      return OSPreferences::DateTimeFormatStyle::Medium;
+    case 3:
+      return OSPreferences::DateTimeFormatStyle::Long;
+    case 4:
+      return OSPreferences::DateTimeFormatStyle::Full;
+  }
+  return OSPreferences::DateTimeFormatStyle::Invalid;
+}
+
+NS_IMETHODIMP
+OSPreferences::GetDateTimePattern(int32_t aDateFormatStyle,
+                                  int32_t aTimeFormatStyle,
+                                  const nsACString& aLocale,
+                                  nsAString& aRetVal)
+{
+  DateTimeFormatStyle dateStyle = ToDateTimeFormatStyle(aDateFormatStyle);
+  if (dateStyle == DateTimeFormatStyle::Invalid) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  DateTimeFormatStyle timeStyle = ToDateTimeFormatStyle(aTimeFormatStyle);
+  if (timeStyle == DateTimeFormatStyle::Invalid) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // If the user is asking for None on both, date and time style,
+  // let's exit early.
+  if (timeStyle == DateTimeFormatStyle::None &&
+      dateStyle == DateTimeFormatStyle::None) {
+    return NS_OK;
+  }
+
+  if (!ReadDateTimePattern(dateStyle, timeStyle, aLocale, aRetVal)) {
+    if (!GetDateTimePatternForStyle(dateStyle, timeStyle, aLocale, aRetVal)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  return NS_OK;
+}
--- a/intl/locale/OSPreferences.h
+++ b/intl/locale/OSPreferences.h
@@ -6,16 +6,18 @@
 #ifndef mozilla_intl_IntlOSPreferences_h__
 #define mozilla_intl_IntlOSPreferences_h__
 
 #include "mozilla/StaticPtr.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "unicode/uloc.h"
 
+#include "mozIOSPreferences.h"
+
 namespace mozilla {
 namespace intl {
 
 /**
  * OSPreferences API provides a set of methods for retrieving information from
  * the host environment on topics such as:
  *   - Internationalization
  *   - Localization
@@ -33,23 +35,52 @@ namespace intl {
  *
  * Second is caching. This API does cache values and where possible will
  * hook into the environment for some event-driven cache invalidation.
  *
  * This means that on platforms that do not support a mechanism to
  * notify apps about changes, new OS-level settings may not be reflected
  * in the app until it is relaunched.
  */
-class OSPreferences
+class OSPreferences: public mozIOSPreferences
 {
 
 public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZIOSPREFERENCES
+
+  enum class DateTimeFormatStyle {
+    Invalid = -1,
+    None,
+    Short,   // e.g. time: HH:mm, date: Y/m/d
+    Medium,  // likely same as Short
+    Long,    // e.g. time: including seconds, date: including weekday
+    Full     // e.g. time: with timezone, date: with long weekday, month
+  };
+
+  /**
+   * Create (if necessary) and return a raw pointer to the singleton instance.
+   * Use this accessor in C++ code that just wants to call a method on the
+   * instance, but does not need to hold a reference, as in
+   *    nsAutoCString str;
+   *    OSPreferences::GetInstance()->GetSystemLocale(str);
+   */
   static OSPreferences* GetInstance();
 
   /**
+   * Return an addRef'd pointer to the singleton instance. This is used by the
+   * XPCOM constructor that exists to support usage from JS.
+   */
+  static already_AddRefed<OSPreferences> GetInstanceAddRefed()
+  {
+    return RefPtr<OSPreferences>(GetInstance()).forget();
+  }
+
+
+  /**
    * Returns a list of locales used by the host environment.
    *
    * The result is a sorted list and we expect that the OS attempts to
    * use the top locale from the list for which it has data.
    *
    * Each element of the list is a valid locale ID that can be passed to ICU
    * and ECMA402 Intl APIs,
    * At the same time each element is a valid BCP47 language tag that can be
@@ -58,35 +89,91 @@ public:
    * Example: ["en-US", "de", "pl", "sr-Cyrl", "zh-Hans-HK"]
    *
    * The return bool value indicates whether the function successfully
    * resolved at least one locale.
    *
    * Usage:
    *   nsTArray<nsCString> systemLocales;
    *   OSPreferences::GetInstance()->GetSystemLocales(systemLocales);
+   *
+   * (See mozIOSPreferences.idl for a JS-callable version of this.)
    */
   bool GetSystemLocales(nsTArray<nsCString>& aRetVal);
 
 protected:
   nsTArray<nsCString> mSystemLocales;
 
 private:
-  static StaticAutoPtr<OSPreferences> sInstance;
+  virtual ~OSPreferences() {};
+
+  static StaticRefPtr<OSPreferences> sInstance;
 
   static bool CanonicalizeLanguageTag(nsCString& aLoc);
 
   /**
+   * Helper methods to get formats from ICU; these will return false
+   * in case of error, in which case the caller cannot rely on aRetVal.
+   */
+  bool GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle,
+                                  DateTimeFormatStyle aTimeStyle,
+                                  const nsACString& aLocale,
+                                  nsAString& aRetVal);
+
+  bool GetDateTimeSkeletonForStyle(DateTimeFormatStyle aDateStyle,
+                                   DateTimeFormatStyle aTimeStyle,
+                                   const nsACString& aLocale,
+                                   nsAString& aRetVal);
+
+  bool GetPatternForSkeleton(const nsAString& aSkeleton,
+                             const nsACString& aLocale,
+                             nsAString& aRetVal);
+
+  bool GetDateTimeConnectorPattern(const nsACString& aLocale,
+                                   nsAString& aRetVal);
+
+  /**
    * This is a host environment specific method that will be implemented
    * separately for each platform.
    *
    * It is only called when the cache is empty or invalidated.
    *
    * The return value indicates whether the function successfully
    * resolved at least one locale.
    */
   bool ReadSystemLocales(nsTArray<nsCString>& aRetVal);
+
+  /**
+   * This is a host environment specific method that will be implemented
+   * separately for each platform.
+   *
+   * It is `best-effort` kind of API that attempts to construct the best
+   * possible date/time pattern for the given styles and locales.
+   *
+   * In case we fail to, or don't know how to retrieve the pattern in a
+   * given environment this function will return false.
+   * Callers should always be prepared to handle that scenario.
+   *
+   * The heuristic may depend on the OS API and HIG guidelines.
+   */
+  bool ReadDateTimePattern(DateTimeFormatStyle aDateFormatStyle,
+                           DateTimeFormatStyle aTimeFormatStyle,
+                           const nsACString& aLocale,
+                           nsAString& aRetVal);
+
+  /**
+   * Triggers a refresh of retrieving data from host environment.
+   *
+   * If the result differs from the previous list, it will additionally
+   * trigger global events for changed values:
+   *
+   *  * SystemLocales: "intl:system-locales-changed"
+   *
+   * This method should not be called from anywhere except of per-platform
+   * hooks into OS events.
+   */
+  void Refresh();
 };
 
 } // intl
 } // namespace mozilla
 
 #endif /* mozilla_intl_IntlOSPreferences_h__ */
new file mode 100644
--- /dev/null
+++ b/intl/locale/gtk/OSPreferences_gtk.cpp
@@ -0,0 +1,174 @@
+/* -*- 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 "dlfcn.h"
+#include "glib.h"
+#include "gio/gio.h"
+
+using namespace mozilla::intl;
+
+bool
+OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList)
+{
+  MOZ_ASSERT(aLocaleList.IsEmpty());
+
+  nsAutoCString defaultLang(uloc_getDefault());
+
+  if (CanonicalizeLanguageTag(defaultLang)) {
+    aLocaleList.AppendElement(defaultLang);
+    return true;
+  }
+  return false;
+}
+
+/*
+ * This looks up into gtk settings for hourCycle format.
+ *
+ * This works for all GUIs that use gtk settings like Gnome, Elementary etc.
+ * Ubuntu does not use those settings so we'll want to support them separately.
+ *
+ * We're taking the current 12/24h settings irrelevant of the locale, because
+ * in the UI user selects this setting for all locales.
+ */
+typedef GVariant* (*get_value_fn_t)(GSettings*, const gchar*);
+
+static get_value_fn_t
+FindGetValueFunction()
+{
+  get_value_fn_t fn = reinterpret_cast<get_value_fn_t>(
+    dlsym(RTLD_DEFAULT, "g_settings_get_user_value")
+  );
+  return fn ? fn : &g_settings_get_value;
+}
+
+static int
+HourCycle()
+{
+  int rval = 0;
+
+  const char* schema;
+  const char* key;
+  const char* env = getenv("XDG_CURRENT_DESKTOP");
+  if (env && strcmp(env, "Unity") == 0) {
+    schema = "com.canonical.indicator.datetime";
+    key = "time-format";
+  } else {
+    schema = "org.gnome.desktop.interface";
+    key = "clock-format";
+  }
+
+  GSettings* settings = g_settings_new(schema);
+  if (settings) {
+    // We really want to use g_settings_get_user_value which will
+    // only want to take it if user manually changed the value.
+    // But this requires glib 2.40, and we still support older glib versions,
+    // so we have to check whether it's available and fall back to the older
+    // g_settings_get_value if not.
+    static get_value_fn_t sGetValueFunction = FindGetValueFunction();
+    GVariant* value = sGetValueFunction(settings, key);
+    if (value) {
+      if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) {
+        const char* strVal = g_variant_get_string(value, nullptr);
+        if (strncmp("12", strVal, 2) == 0) {
+          rval = 12;
+        } else if (strncmp("24", strVal, 2) == 0) {
+          rval = 24;
+        }
+      }
+      g_variant_unref(value);
+    }
+    g_object_unref(settings);
+  }
+  return rval;
+}
+
+/**
+ * Since Gtk does not provide a way to customize or format date/time patterns,
+ * we're reusing ICU data here, but we do modify it according to the only
+ * setting Gtk gives us - hourCycle.
+ *
+ * This means that for gtk we will return a pattern from ICU altered to
+ * represent h12/h24 hour cycle if the user modified the default value.
+ *
+ * In short, this should work like this:
+ *
+ *  * gtk defaults, pl: 24h
+ *  * gtk defaults, en: 12h
+ *
+ *  * gtk 12h, pl: 12h
+ *  * gtk 12h, en: 12h
+ *
+ *  * gtk 24h, pl: 24h
+ *  * gtk 12h, en: 12h
+ */
+bool
+OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
+                                   DateTimeFormatStyle aTimeStyle,
+                                   const nsACString& aLocale, nsAString& aRetVal)
+{
+  nsAutoString skeleton;
+  if (!GetDateTimeSkeletonForStyle(aDateStyle, aTimeStyle, aLocale, skeleton)) {
+    return false;
+  }
+
+  // Customize the skeleton if necessary to reflect user's 12/24hr pref
+  switch (HourCycle()) {
+    case 12: {
+      // If skeleton contains 'H' or 'k', replace with 'h' or 'K' respectively,
+      // and add 'a' unless already present.
+      if (skeleton.FindChar('H') == -1 && skeleton.FindChar('k') == -1) {
+        break; // nothing to do
+      }
+      bool foundA = false;
+      for (size_t i = 0; i < skeleton.Length(); ++i) {
+        switch (skeleton[i]) {
+          case 'a':
+            foundA = true;
+            break;
+          case 'H':
+            skeleton.SetCharAt('h', i);
+            break;
+          case 'k':
+            skeleton.SetCharAt('K', i);
+            break;
+        }
+      }
+      if (!foundA) {
+        skeleton.Append(char16_t('a'));
+      }
+      break;
+    }
+    case 24:
+      // If skeleton contains 'h' or 'K', replace with 'H' or 'k' respectively,
+      // and delete 'a' if present.
+      if (skeleton.FindChar('h') == -1 && skeleton.FindChar('K') == -1) {
+        break; // nothing to do
+      }
+      for (int32_t i = 0; i < int32_t(skeleton.Length()); ++i) {
+        switch (skeleton[i]) {
+          case 'a':
+            skeleton.Cut(i, 1);
+            --i;
+            break;
+          case 'h':
+            skeleton.SetCharAt('H', i);
+            break;
+          case 'K':
+            skeleton.SetCharAt('k', i);
+            break;
+        }
+      }
+      break;
+  }
+
+  if (!GetPatternForSkeleton(skeleton, aLocale, aRetVal)) {
+    return false;
+  }
+
+  return true;
+}
+
new file mode 100644
--- /dev/null
+++ b/intl/locale/gtk/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+SOURCES += ['OSPreferences_gtk.cpp']
+
+CXXFLAGS += CONFIG['GLIB_CFLAGS']
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '..',
+]
--- a/intl/locale/mac/OSPreferences_mac.cpp
+++ b/intl/locale/mac/OSPreferences_mac.cpp
@@ -33,8 +33,86 @@ OSPreferences::ReadSystemLocales(nsTArra
 
   if (CanonicalizeLanguageTag(locale)) {
     aLocaleList.AppendElement(locale);
     return true;
   }
 
   return false;
 }
+
+static CFDateFormatterStyle
+ToCFDateFormatterStyle(OSPreferences::DateTimeFormatStyle aFormatStyle)
+{
+  switch (aFormatStyle) {
+    case OSPreferences::DateTimeFormatStyle::None:
+      return kCFDateFormatterNoStyle;
+    case OSPreferences::DateTimeFormatStyle::Short:
+      return kCFDateFormatterShortStyle;
+    case OSPreferences::DateTimeFormatStyle::Medium:
+      return kCFDateFormatterMediumStyle;
+    case OSPreferences::DateTimeFormatStyle::Long:
+      return kCFDateFormatterLongStyle;
+    case OSPreferences::DateTimeFormatStyle::Full:
+      return kCFDateFormatterFullStyle;
+    case OSPreferences::DateTimeFormatStyle::Invalid:
+      MOZ_ASSERT_UNREACHABLE("invalid time format");
+      return kCFDateFormatterNoStyle;
+  }
+}
+
+// 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)
+{
+  if (aLocale.IsEmpty()) {
+    return CFLocaleCopyCurrent();
+  }
+  CFStringRef identifier =
+    CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
+                                  (const uint8_t*)aLocale.BeginReading(),
+                                  aLocale.Length(), kCFStringEncodingASCII,
+                                  false, kCFAllocatorNull);
+  if (!identifier) {
+    return nullptr;
+  }
+  CFLocaleRef locale = CFLocaleCreate(kCFAllocatorDefault, identifier);
+  CFRelease(identifier);
+  return locale;
+}
+
+/**
+ * Cocoa API maps nicely to our four styles of date/time.
+ *
+ * The only caveat is that Cocoa takes regional preferences modifications
+ * into account only when we pass an empty string as a locale.
+ *
+ * In all other cases it will return the default pattern for a given locale.
+ */
+bool
+OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
+                                   DateTimeFormatStyle aTimeStyle,
+                                   const nsACString& aLocale, nsAString& aRetVal)
+{
+  CFLocaleRef locale = CreateCFLocaleFor(aLocale);
+  if (!locale) {
+    return false;
+  }
+
+  CFDateFormatterRef formatter =
+    CFDateFormatterCreate(kCFAllocatorDefault, locale,
+                          ToCFDateFormatterStyle(aDateStyle),
+                          ToCFDateFormatterStyle(aTimeStyle));
+  CFStringRef format = CFDateFormatterGetFormat(formatter);
+  CFRelease(locale);
+
+  CFRange range = CFRangeMake(0, CFStringGetLength(format));
+  aRetVal.SetLength(range.length);
+  CFStringGetCharacters(format, range,
+                        reinterpret_cast<UniChar*>(aRetVal.BeginWriting()));
+  CFRelease(formatter);
+
+  return true;
+}
--- a/intl/locale/moz.build
+++ b/intl/locale/moz.build
@@ -15,19 +15,22 @@ if CONFIG['ENABLE_INTL_API']:
 toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
 
 if toolkit == 'windows':
     DIRS += ['windows']
 elif toolkit == 'cocoa':
     DIRS += ['mac']
 else:
     DIRS += ['unix']
+    if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+        DIRS += ['gtk']
 
 XPIDL_SOURCES += [
     'mozILocaleService.idl',
+    'mozIOSPreferences.idl',
     'nsICollation.idl',
     'nsILocale.idl',
     'nsILocaleService.idl',
     'nsIScriptableDateFormat.idl',
 ]
 
 XPIDL_MODULE = 'locale'
 
new file mode 100644
--- /dev/null
+++ b/intl/locale/mozIOSPreferences.idl
@@ -0,0 +1,86 @@
+/* -*- Mode: IDL; 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 "nsISupports.idl"
+
+%{C++
+// Define Contractid and CID
+#define MOZ_OSPREFERENCES_CID \
+  { 0x65944815, 0xe9ae, 0x48bd, { 0xa2, 0xbf, 0xf1, 0x10, 0x87, 0x20, 0x95, 0x0c } }
+
+#define MOZ_OSPREFERENCES_CONTRACTID "@mozilla.org/intl/ospreferences;1"
+%}
+
+[scriptable, uuid(65944815-e9ae-48bd-a2bf-f1108720950c)]
+interface mozIOSPreferences : nsISupports
+{
+  const long dateTimeFormatStyleNone     = 0;
+  const long dateTimeFormatStyleShort    = 1;
+  const long dateTimeFormatStyleMedium   = 2;
+  const long dateTimeFormatStyleLong     = 3;
+  const long dateTimeFormatStyleFull     = 4;
+
+  /**
+   * Returns a list of locales used by the host environment.
+   *
+   * The result is a sorted list and we expect that the OS attempts to
+   * use the top locale from the list for which it has data.
+   *
+   * Each element of the list is a valid locale ID that can be passed to ICU
+   * and ECMA402 Intl APIs,
+   * At the same time each element is a valid BCP47 language tag that can be
+   * used for language negotiation.
+   *
+   * Example: ["en-US", "de", "pl", "sr-Cyrl", "zh-Hans-HK"]
+   *
+   * The API may return an empty list in case no locale could
+   * be retrieved from the system.
+   *
+   * (See OSPreferences.h for a more C++-friendly version of this.)
+   */
+  void getSystemLocales([optional] out unsigned long aCount,
+                        [retval, array, size_is(aCount)] out string aOutArray);
+
+  /**
+   * Returns the best locale that the host environment is localized to.
+   *
+   * The result is a valid locale ID and it should be
+   * used for all APIs that do not handle language negotiation.
+   *
+   * In any scenario involving language negotiation, GetSystemLocales should
+   * be preferred over the single value.
+   *
+   * Example: "zh-Hans-HK"
+   */
+  readonly attribute ACString systemLocale;
+
+  /**
+   * Returns the best possible date/time pattern for the host environment
+   * taking into account date/time regional settings user defined in the OS
+   * preferences.
+   *
+   * Notice, that depending on the OS it may take into account those settings
+   * for all locales, or only if the locale matches the OS locale.
+   *
+   * It takes two integer arguments that must be valid `dateTimeFormatStyle*`
+   * values (see constants defined above), and a string representing a
+   * BCP47 locale.
+   *
+   * It returns a string with a LDML date/time pattern.
+   *
+   * If no pattern can be retrieved from the host environment, it will
+   * lookup the best available pattern from ICU.
+   *
+   * Notice, this is a pretty unique method in this API in that it does
+   * more than look up into host environment.
+   * The reason for that is that constructing the right date/time pattern
+   * requires a lot of OS-specific logic and it ends up being easier to just
+   * handle all scenarios, including with cases where we fail to retrieve
+   * anything from the OS, here.
+   */
+  AString getDateTimePattern(in long timeFormatStyle,
+                             in long dateFormatStyle,
+                             [optional] in ACString locale);
+};
--- a/intl/locale/nsLocaleConstructors.h
+++ b/intl/locale/nsLocaleConstructors.h
@@ -9,16 +9,17 @@
 #include "nsCollationCID.h"
 #include "mozilla/ModuleUtils.h"
 #include "nsILocaleService.h"
 #include "nsIScriptableDateFormat.h"
 #include "nsIServiceManager.h"
 #include "nsLanguageAtomService.h"
 #include "nsPlatformCharset.h"
 #include "LocaleService.h"
+#include "OSPreferences.h"
 
 #if defined(XP_MACOSX)
 #define USE_MAC_LOCALE
 #endif
 
 #if defined(XP_UNIX) && !defined(XP_MACOSX)
 #define USE_UNIX_LOCALE
 #endif
@@ -57,16 +58,18 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollati
 //NS_GENERIC_FACTORY_CONSTRUCTOR(nsScriptableDateTimeFormat)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsLanguageAtomService)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPlatformCharset, Init)
 
 namespace mozilla {
 namespace intl {
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(LocaleService,
                                          LocaleService::GetInstanceAddRefed)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(OSPreferences,
+                                         OSPreferences::GetInstanceAddRefed)
 }
 }
 
 #ifdef XP_WIN
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollationWin)
 #endif
 
 #ifdef USE_UNIX_LOCALE
--- a/intl/locale/tests/gtest/TestOSPreferences.cpp
+++ b/intl/locale/tests/gtest/TestOSPreferences.cpp
@@ -1,14 +1,15 @@
 /* -*- 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 "gtest/gtest.h"
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/intl/OSPreferences.h"
 
 using namespace mozilla::intl;
 
 /**
  * We test that on all platforms we test against (irrelevant of the tier),
  * we will be able to retrieve at least a single locale out of the system.
  *
@@ -17,8 +18,56 @@ using namespace mozilla::intl;
  * it not happen without us noticing.
  */
 TEST(Intl_Locale_OSPreferences, GetSystemLocales) {
   nsTArray<nsCString> systemLocales;
   ASSERT_TRUE(OSPreferences::GetInstance()->GetSystemLocales(systemLocales));
 
   ASSERT_FALSE(systemLocales.IsEmpty());
 }
+
+/**
+ * We test that on all platforms we test against,
+ * we will be able to retrieve a date and time pattern.
+ *
+ * This may come back empty on platforms where we don't have platforms
+ * bindings for, so effectively, we're testing for crashes. We should
+ * never crash.
+ */
+TEST(Intl_Locale_OSPreferences, GetDateTimePattern) {
+  nsAutoString pattern;
+  OSPreferences* osprefs = OSPreferences::GetInstance();
+
+  struct Test {
+    int dateStyle;
+    int timeStyle;
+    const char* locale;
+  };
+  Test tests[] = {
+    { 0, 0, "" },
+    { 1, 0, "pl" },
+    { 2, 0, "de-DE" },
+    { 3, 0, "fr" },
+    { 4, 0, "ar" },
+
+    { 0, 1, "" },
+    { 0, 2, "it" },
+    { 0, 3, "" },
+    { 0, 4, "ru" },
+
+    { 4, 1, "" },
+    { 3, 2, "cs" },
+    { 2, 3, "" },
+    { 1, 4, "ja" }
+  };
+
+  for (unsigned i = 0; i < mozilla::ArrayLength(tests); i++) {
+    const Test& t = tests[i];
+    nsAutoString pattern;
+    if (NS_SUCCEEDED(osprefs->GetDateTimePattern(t.dateStyle, t.timeStyle,
+                                                 nsDependentCString(t.locale),
+                                                 pattern))) {
+      ASSERT_TRUE((t.dateStyle == 0 && t.timeStyle == 0) || !pattern.IsEmpty());
+    }
+  }
+
+  ASSERT_TRUE(1);
+}
new file mode 100644
--- /dev/null
+++ b/intl/locale/tests/unit/test_osPreferences.js
@@ -0,0 +1,38 @@
+/* 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/. */
+
+function run_test()
+{
+  const osprefs =
+    Components.classes["@mozilla.org/intl/ospreferences;1"]
+    .getService(Components.interfaces.mozIOSPreferences);
+
+  const systemLocale = osprefs.systemLocale;
+  do_check_true(systemLocale != "", "systemLocale is non-empty");
+
+  const systemLocales = osprefs.getSystemLocales();
+  do_check_true(Array.isArray(systemLocales), "systemLocales returns an array");
+
+  do_check_true(systemLocale == systemLocales[0],
+    "systemLocale matches first entry in systemLocales");
+
+  const getDateTimePatternTests = [
+    [osprefs.dateTimeFormatStyleNone, osprefs.dateTimeFormatStyleNone, ""],
+    [osprefs.dateTimeFormatStyleShort, osprefs.dateTimeFormatStyleNone, ""],
+    [osprefs.dateTimeFormatStyleNone, osprefs.dateTimeFormatStyleLong, "ar"],
+    [osprefs.dateTimeFormatStyleFull, osprefs.dateTimeFormatStyleMedium, "ru"],
+  ];
+
+  for (let i = 0; i < getDateTimePatternTests.length; i++) {
+    const test = getDateTimePatternTests[i];
+
+    const pattern = osprefs.getDateTimePattern(...test);
+    if (test[0] !== osprefs.dateTimeFormatStyleNone &&
+        test[1] !== osprefs.dateTImeFormatStyleNone) {
+      do_check_true(pattern.length > 0, "pattern is not empty.");
+    }
+  }
+
+  do_check_true(1, "osprefs didn't crash");
+}
--- a/intl/locale/tests/unit/xpcshell.ini
+++ b/intl/locale/tests/unit/xpcshell.ini
@@ -18,8 +18,9 @@ skip-if = toolkit != "cocoa"
 [test_intl_on_workers.js]
 skip-if = toolkit == "android" # bug 1309447
 
 [test_pluralForm.js]
 [test_pluralForm_english.js]
 [test_pluralForm_makeGetter.js]
 
 [test_localeService.js]
+[test_osPreferences.js]
--- a/intl/locale/unix/OSPreferences_unix.cpp
+++ b/intl/locale/unix/OSPreferences_unix.cpp
@@ -16,8 +16,16 @@ OSPreferences::ReadSystemLocales(nsTArra
   nsAutoCString defaultLang(uloc_getDefault());
 
   if (CanonicalizeLanguageTag(defaultLang)) {
     aLocaleList.AppendElement(defaultLang);
     return true;
   }
   return false;
 }
+
+bool
+OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
+                                   DateTimeFormatStyle aTimeStyle,
+                                   const nsACString& aLocale, nsAString& aRetVal)
+{
+  return false;
+}
--- a/intl/locale/unix/moz.build
+++ b/intl/locale/unix/moz.build
@@ -4,17 +4,17 @@
 # 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/.
 
 SOURCES += [
     'nsCollationUnix.cpp',
     'nsPosixLocale.cpp',
 ]
 
-if CONFIG['ENABLE_INTL_API']:
+if 'gtk' not in CONFIG['MOZ_WIDGET_TOOLKIT']:
     SOURCES += ['OSPreferences_unix.cpp']
 
 if CONFIG['OS_TARGET'] == 'Android':
     SOURCES += [
         'nsAndroidCharset.cpp',
     ]
 else:
     SOURCES += [
--- a/intl/locale/windows/OSPreferences_win.cpp
+++ b/intl/locale/windows/OSPreferences_win.cpp
@@ -22,8 +22,187 @@ OSPreferences::ReadSystemLocales(nsTArra
   NS_LossyConvertUTF16toASCII loc(locale);
 
   if (CanonicalizeLanguageTag(loc)) {
     aLocaleList.AppendElement(loc);
     return true;
   }
   return false;
 }
+
+static LCTYPE
+ToDateLCType(OSPreferences::DateTimeFormatStyle aFormatStyle)
+{
+  switch (aFormatStyle) {
+    case OSPreferences::DateTimeFormatStyle::None:
+      return LOCALE_SLONGDATE;
+    case OSPreferences::DateTimeFormatStyle::Short:
+      return LOCALE_SSHORTDATE;
+    case OSPreferences::DateTimeFormatStyle::Medium:
+      return LOCALE_SSHORTDATE;
+    case OSPreferences::DateTimeFormatStyle::Long:
+      return LOCALE_SLONGDATE;
+    case OSPreferences::DateTimeFormatStyle::Full:
+      return LOCALE_SLONGDATE;
+    case OSPreferences::DateTimeFormatStyle::Invalid:
+    default:
+      MOZ_ASSERT_UNREACHABLE("invalid date format");
+      return LOCALE_SLONGDATE;
+  }
+}
+
+static LCTYPE
+ToTimeLCType(OSPreferences::DateTimeFormatStyle aFormatStyle)
+{
+  switch (aFormatStyle) {
+    case OSPreferences::DateTimeFormatStyle::None:
+      return LOCALE_STIMEFORMAT;
+    case OSPreferences::DateTimeFormatStyle::Short:
+      return LOCALE_SSHORTTIME;
+    case OSPreferences::DateTimeFormatStyle::Medium:
+      return LOCALE_SSHORTTIME;
+    case OSPreferences::DateTimeFormatStyle::Long:
+      return LOCALE_STIMEFORMAT;
+    case OSPreferences::DateTimeFormatStyle::Full:
+      return LOCALE_STIMEFORMAT;
+    case OSPreferences::DateTimeFormatStyle::Invalid:
+    default:
+      MOZ_ASSERT_UNREACHABLE("invalid time format");
+      return LOCALE_STIMEFORMAT;
+  }
+}
+
+/**
+ * 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:
+ *
+ *   short  -> short
+ *   medium -> short
+ *   long   -> long
+ *   full   -> long
+ *
+ * In order to produce a single date/time format, we use CLDR pattern
+ * 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();
+  }
+
+  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) {
+    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 {
+    aRetVal.Truncate(0);
+    return true;
+  }
+
+  if (isDate) {
+    LCTYPE lcType = ToDateLCType(aDateStyle);
+    size_t len = GetLocaleInfoEx(localeName, lcType, nullptr, 0);
+    if (len == 0) {
+      return false;
+    }
+    str->SetLength(len - 1); // -1 because len counts the null terminator
+    GetLocaleInfoEx(localeName, lcType, (WCHAR*)str->BeginWriting(), len);
+
+    // Windows uses "ddd" and "dddd" for abbreviated and full day names respectively,
+    //   https://msdn.microsoft.com/en-us/library/windows/desktop/dd317787(v=vs.85).aspx
+    // but in a CLDR/ICU-style pattern these should be "EEE" and "EEEE".
+    //   http://userguide.icu-project.org/formatparse/datetime
+    // So we fix that up here.
+    nsAString::const_iterator start, pos, end;
+    start = str->BeginReading(pos);
+    str->EndReading(end);
+    if (FindInReadable(NS_LITERAL_STRING("dddd"), pos, end)) {
+      str->Replace(pos - start, 4, NS_LITERAL_STRING("EEEE"));
+    } else if (FindInReadable(NS_LITERAL_STRING("ddd"), pos, end)) {
+      str->Replace(pos - start, 3, NS_LITERAL_STRING("EEE"));
+    }
+
+    // Also, Windows uses lowercase "g" or "gg" for era, but ICU wants uppercase "G"
+    // (it would interpret "g" as "modified Julian day"!). So fix that.
+    int32_t index = str->FindChar('g');
+    if (index >= 0) {
+      str->Replace(index, 1, 'G');
+      // If it was a double "gg", just drop the second one.
+      index++;
+      if (str->CharAt(index) == 'g') {
+        str->Cut(index, 1);
+      }
+    }
+
+    // If time was also requested, we need to substitute the date pattern from Windows
+    // into the date+time format that we have in aRetVal.
+    if (isTime) {
+      nsAString::const_iterator start, pos, end;
+      start = aRetVal.BeginReading(pos);
+      aRetVal.EndReading(end);
+      if (FindInReadable(NS_LITERAL_STRING("{1}"), pos, end)) {
+        aRetVal.Replace(pos - start, 3, tmpStr);
+      }
+    }
+  }
+
+  if (isTime) {
+    LCTYPE lcType = ToTimeLCType(aTimeStyle);
+    size_t len = GetLocaleInfoEx(localeName, lcType, nullptr, 0);
+    if (len == 0) {
+      return false;
+    }
+    str->SetLength(len - 1);
+    GetLocaleInfoEx(localeName, lcType, (WCHAR*)str->BeginWriting(), len);
+
+    // Windows uses "t" or "tt" for a "time marker" (am/pm indicator),
+    //   https://msdn.microsoft.com/en-us/library/windows/desktop/dd318148(v=vs.85).aspx
+    // but in a CLDR/ICU-style pattern that should be "a".
+    //   http://userguide.icu-project.org/formatparse/datetime
+    // So we fix that up here.
+    int32_t index = str->FindChar('t');
+    if (index >= 0) {
+      str->Replace(index, 1, 'a');
+      index++;
+      if (str->CharAt(index) == 't') {
+        str->Cut(index, 1);
+      }
+    }
+
+    if (isDate) {
+      nsAString::const_iterator start, pos, end;
+      start = aRetVal.BeginReading(pos);
+      aRetVal.EndReading(end);
+      if (FindInReadable(NS_LITERAL_STRING("{0}"), pos, end)) {
+        aRetVal.Replace(pos - start, 3, tmpStr);
+      }
+    }
+  }
+
+  return true;
+}