--- 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;
+}