/* -*- 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>* 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 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(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((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 to * an int, to be used as a caching key. */ static int StyleToInt(const Maybe& 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>( kMaxCachedFormats); } UniquePtr& 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> timeZoneOverride = Nothing(); nsAutoString timeZoneID; if (aTimeParameters) { BuildTimeZoneString(*aTimeParameters, timeZoneID); timeZoneOverride = Some(Span(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