Bug 1308329 - Extend OSPreferences API to cover date/time styles. r=jfkthame
authorZibi Braniecki <gandalf@mozilla.com>
Wed, 08 Feb 2017 17:17:51 -0800
changeset 394476 0fb088fb7d66147ff48d99596dfd3f67960d6426
parent 394475 6085498051cc33bee189d1c2c71816194d456401
child 394477 0f9b03faff358ed78b951b0f67401f8bd108bf34
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame
bugs1308329
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;
+}