Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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