diff options
Diffstat (limited to 'intl/locale/AppDateTimeFormat.cpp')
-rw-r--r-- | intl/locale/AppDateTimeFormat.cpp | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/intl/locale/AppDateTimeFormat.cpp b/intl/locale/AppDateTimeFormat.cpp new file mode 100644 index 0000000000..d967d312a4 --- /dev/null +++ b/intl/locale/AppDateTimeFormat.cpp @@ -0,0 +1,263 @@ +/* -*- 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 "nsCOMPtr.h" +#include "mozilla/intl/AppDateTimeFormat.h" +#include "mozilla/intl/DateTimePatternGenerator.h" +#include "mozilla/intl/FormatBuffer.h" +#include "mozilla/intl/LocaleService.h" +#include "OSPreferences.h" +#include "mozIOSPreferences.h" +#ifdef DEBUG +# include "nsThreadManager.h" +#endif + +namespace mozilla::intl { + +nsCString* AppDateTimeFormat::sLocale = nullptr; +nsTHashMap<nsCStringHashKey, UniquePtr<DateTimeFormat>>* + AppDateTimeFormat::sFormatCache; + +static const int32_t DATETIME_FORMAT_INITIAL_LEN = 127; + +/*static*/ +nsresult AppDateTimeFormat::Initialize() { + MOZ_ASSERT(NS_IsMainThread()); + if (sLocale) { + return NS_OK; + } + + sLocale = new nsCString(); + AutoTArray<nsCString, 10> regionalPrefsLocales; + LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales); + sLocale->Assign(regionalPrefsLocales[0]); + + return NS_OK; +} + +// performs a locale sensitive date formatting operation on the PRTime parameter +/*static*/ +nsresult AppDateTimeFormat::Format(const DateTimeFormat::StyleBag& aStyle, + const PRTime aPrTime, + nsAString& aStringOut) { + return AppDateTimeFormat::Format( + aStyle, (static_cast<double>(aPrTime) / PR_USEC_PER_MSEC), nullptr, + aStringOut); +} + +// performs a locale sensitive date formatting operation on the PRExplodedTime +// parameter +/*static*/ +nsresult AppDateTimeFormat::Format(const DateTimeFormat::StyleBag& aStyle, + const PRExplodedTime* aExplodedTime, + nsAString& aStringOut) { + return AppDateTimeFormat::Format( + aStyle, (PR_ImplodeTime(aExplodedTime) / PR_USEC_PER_MSEC), + &(aExplodedTime->tm_params), aStringOut); +} + +// performs a locale sensitive date formatting operation on the PRExplodedTime +// parameter, using the specified options. +/*static*/ +nsresult AppDateTimeFormat::Format(const DateTimeFormat::ComponentsBag& aBag, + const PRExplodedTime* aExplodedTime, + nsAString& aStringOut) { + // set up locale data + nsresult rv = Initialize(); + if (NS_FAILED(rv)) { + return rv; + } + + aStringOut.Truncate(); + + nsAutoCString str; + nsAutoString timeZoneID; + BuildTimeZoneString(aExplodedTime->tm_params, timeZoneID); + + auto genResult = DateTimePatternGenerator::TryCreate(sLocale->get()); + NS_ENSURE_TRUE(genResult.isOk(), NS_ERROR_FAILURE); + auto dateTimePatternGenerator = genResult.unwrap(); + + auto result = DateTimeFormat::TryCreateFromComponents( + *sLocale, aBag, dateTimePatternGenerator.get(), Some(timeZoneID)); + NS_ENSURE_TRUE(result.isOk(), NS_ERROR_FAILURE); + auto dateTimeFormat = result.unwrap(); + + double unixEpoch = + static_cast<float>((PR_ImplodeTime(aExplodedTime) / PR_USEC_PER_MSEC)); + + aStringOut.SetLength(DATETIME_FORMAT_INITIAL_LEN); + nsTStringToBufferAdapter buffer(aStringOut); + NS_ENSURE_TRUE(dateTimeFormat->TryFormat(unixEpoch, buffer).isOk(), + NS_ERROR_FAILURE); + + return rv; +} + +/** + * An internal utility function to serialize a Maybe<DateTimeFormat::Style> to + * an int, to be used as a caching key. + */ +static int StyleToInt(const Maybe<DateTimeFormat::Style>& aStyle) { + if (aStyle.isSome()) { + switch (*aStyle) { + case DateTimeFormat::Style::Full: + return 1; + case DateTimeFormat::Style::Long: + return 2; + case DateTimeFormat::Style::Medium: + return 3; + case DateTimeFormat::Style::Short: + return 4; + } + } + return 0; +} + +/*static*/ +nsresult AppDateTimeFormat::Format(const DateTimeFormat::StyleBag& aStyle, + const double aUnixEpoch, + const PRTimeParameters* aTimeParameters, + nsAString& aStringOut) { + nsresult rv = NS_OK; + + // return, nothing to format + if (aStyle.date.isNothing() && aStyle.time.isNothing()) { + aStringOut.Truncate(); + return NS_OK; + } + + // set up locale data + rv = Initialize(); + + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoCString key; + key.AppendInt(StyleToInt(aStyle.date)); + key.Append(':'); + key.AppendInt(StyleToInt(aStyle.time)); + if (aTimeParameters) { + key.Append(':'); + key.AppendInt(aTimeParameters->tp_gmt_offset); + key.Append(':'); + key.AppendInt(aTimeParameters->tp_dst_offset); + } + + if (sFormatCache && sFormatCache->Count() == kMaxCachedFormats) { + // Don't allow a pathological page to extend the cache unreasonably. + NS_WARNING("flushing DateTimeFormat cache"); + DeleteCache(); + } + if (!sFormatCache) { + sFormatCache = new nsTHashMap<nsCStringHashKey, UniquePtr<DateTimeFormat>>( + kMaxCachedFormats); + } + + UniquePtr<DateTimeFormat>& dateTimeFormat = sFormatCache->LookupOrInsert(key); + + if (!dateTimeFormat) { + // We didn't have a cached formatter for this key, so create one. + int32_t dateFormatStyle = mozIOSPreferences::dateTimeFormatStyleNone; + if (aStyle.date.isSome()) { + switch (*aStyle.date) { + case DateTimeFormat::Style::Full: + case DateTimeFormat::Style::Long: + dateFormatStyle = mozIOSPreferences::dateTimeFormatStyleLong; + break; + case DateTimeFormat::Style::Medium: + case DateTimeFormat::Style::Short: + dateFormatStyle = mozIOSPreferences::dateTimeFormatStyleShort; + break; + } + } + + int32_t timeFormatStyle = mozIOSPreferences::dateTimeFormatStyleNone; + if (aStyle.time.isSome()) { + switch (*aStyle.time) { + case DateTimeFormat::Style::Full: + case DateTimeFormat::Style::Long: + timeFormatStyle = mozIOSPreferences::dateTimeFormatStyleLong; + break; + case DateTimeFormat::Style::Medium: + case DateTimeFormat::Style::Short: + timeFormatStyle = mozIOSPreferences::dateTimeFormatStyleShort; + break; + } + } + + nsAutoCString str; + rv = OSPreferences::GetInstance()->GetDateTimePattern( + dateFormatStyle, timeFormatStyle, nsDependentCString(sLocale->get()), + str); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString pattern = NS_ConvertUTF8toUTF16(str); + + Maybe<Span<const char16_t>> timeZoneOverride = Nothing(); + nsAutoString timeZoneID; + if (aTimeParameters) { + BuildTimeZoneString(*aTimeParameters, timeZoneID); + timeZoneOverride = + Some(Span<const char16_t>(timeZoneID.Data(), timeZoneID.Length())); + } + + auto result = DateTimeFormat::TryCreateFromPattern(*sLocale, pattern, + timeZoneOverride); + NS_ENSURE_TRUE(result.isOk(), NS_ERROR_FAILURE); + dateTimeFormat = result.unwrap(); + } + + MOZ_ASSERT(dateTimeFormat); + + aStringOut.SetLength(DATETIME_FORMAT_INITIAL_LEN); + nsTStringToBufferAdapter buffer(aStringOut); + NS_ENSURE_TRUE(dateTimeFormat->TryFormat(aUnixEpoch, buffer).isOk(), + NS_ERROR_FAILURE); + + return rv; +} + +/*static*/ +void AppDateTimeFormat::BuildTimeZoneString( + const PRTimeParameters& aTimeParameters, nsAString& aStringOut) { + aStringOut.Truncate(); + aStringOut.Append(u"GMT"); + int32_t totalOffsetMinutes = + (aTimeParameters.tp_gmt_offset + aTimeParameters.tp_dst_offset) / 60; + if (totalOffsetMinutes != 0) { + char sign = totalOffsetMinutes < 0 ? '-' : '+'; + int32_t hours = abs(totalOffsetMinutes) / 60; + int32_t minutes = abs(totalOffsetMinutes) % 60; + aStringOut.AppendPrintf("%c%02d:%02d", sign, hours, minutes); + } +} + +/*static*/ +void AppDateTimeFormat::DeleteCache() { + MOZ_ASSERT(NS_IsMainThread()); + if (sFormatCache) { + delete sFormatCache; + sFormatCache = nullptr; + } +} + +/*static*/ +void AppDateTimeFormat::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + DeleteCache(); + delete sLocale; +} + +/*static*/ +void AppDateTimeFormat::ClearLocaleCache() { + MOZ_ASSERT(NS_IsMainThread()); + DeleteCache(); + delete sLocale; + sLocale = nullptr; +} + +} // namespace mozilla::intl |