diff options
Diffstat (limited to 'intl/locale/DateTimeFormat.cpp')
-rw-r--r-- | intl/locale/DateTimeFormat.cpp | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/intl/locale/DateTimeFormat.cpp b/intl/locale/DateTimeFormat.cpp new file mode 100644 index 0000000000..be8cad0479 --- /dev/null +++ b/intl/locale/DateTimeFormat.cpp @@ -0,0 +1,373 @@ +/* -*- 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 "DateTimeFormat.h" +#include "nsCOMPtr.h" +#include "mozilla/intl/LocaleService.h" +#include "OSPreferences.h" +#include "mozIOSPreferences.h" +#include "unicode/dtfmtsym.h" +#include "unicode/udatpg.h" + +namespace mozilla { +using namespace mozilla::intl; + +nsCString* DateTimeFormat::mLocale = nullptr; +nsDataHashtable<nsCStringHashKey, UDateFormat*>* DateTimeFormat::mFormatCache; + +static const int32_t DATETIME_FORMAT_INITIAL_LEN = 127; + +/*static*/ +nsresult DateTimeFormat::Initialize() { + if (mLocale) { + return NS_OK; + } + + mLocale = new nsCString(); + AutoTArray<nsCString, 10> regionalPrefsLocales; + intl::LocaleService::GetInstance()->GetRegionalPrefsLocales( + regionalPrefsLocales); + mLocale->Assign(regionalPrefsLocales[0]); + + return NS_OK; +} + +// performs a locale sensitive date formatting operation on the PRTime parameter +/*static*/ +nsresult DateTimeFormat::FormatPRTime( + const nsDateFormatSelector aDateFormatSelector, + const nsTimeFormatSelector aTimeFormatSelector, const PRTime aPrTime, + nsAString& aStringOut) { + return FormatUDateTime(aDateFormatSelector, aTimeFormatSelector, + (aPrTime / PR_USEC_PER_MSEC), nullptr, aStringOut); +} + +// performs a locale sensitive date formatting operation on the PRExplodedTime +// parameter +/*static*/ +nsresult DateTimeFormat::FormatPRExplodedTime( + const nsDateFormatSelector aDateFormatSelector, + const nsTimeFormatSelector aTimeFormatSelector, + const PRExplodedTime* aExplodedTime, nsAString& aStringOut) { + return FormatUDateTime(aDateFormatSelector, aTimeFormatSelector, + (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 DateTimeFormat::FormatDateTime( + const PRExplodedTime* aExplodedTime, + const DateTimeFormat::Skeleton aSkeleton, nsAString& aStringOut) { + // set up locale data + nsresult rv = Initialize(); + if (NS_FAILED(rv)) { + return rv; + } + + aStringOut.Truncate(); + + UErrorCode status = U_ZERO_ERROR; + + nsAutoCString skeleton; + switch (aSkeleton) { + case Skeleton::yyyyMM: + skeleton.AssignASCII("yyyyMM"); + break; + case Skeleton::yyyyMMMM: + skeleton.AssignASCII("yyyyMMMM"); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unhandled skeleton enum"); + } + + nsAutoCString str; + if (!OSPreferences::GetPatternForSkeleton(skeleton, *mLocale, str)) { + return NS_ERROR_FAILURE; + } + nsAutoString pattern = NS_ConvertUTF8toUTF16(str); + + nsAutoString timeZoneID; + BuildTimeZoneString(aExplodedTime->tm_params, timeZoneID); + + UDateFormat* dateTimeFormat = + udat_open(UDAT_PATTERN, UDAT_PATTERN, mLocale->get(), + reinterpret_cast<const UChar*>(timeZoneID.BeginReading()), + timeZoneID.Length(), + reinterpret_cast<const UChar*>(pattern.BeginReading()), + pattern.Length(), &status); + + if (U_SUCCESS(status) && dateTimeFormat) { + UDate udate = + static_cast<float>((PR_ImplodeTime(aExplodedTime) / PR_USEC_PER_MSEC)); + + aStringOut.SetLength(DATETIME_FORMAT_INITIAL_LEN); + int32_t dateTimeLen = + udat_format(dateTimeFormat, udate, + reinterpret_cast<UChar*>(aStringOut.BeginWriting()), + DATETIME_FORMAT_INITIAL_LEN, nullptr, &status); + aStringOut.SetLength(dateTimeLen); + + if (status == U_BUFFER_OVERFLOW_ERROR) { + status = U_ZERO_ERROR; + udat_format(dateTimeFormat, udate, + reinterpret_cast<UChar*>(aStringOut.BeginWriting()), + dateTimeLen, nullptr, &status); + } + } + + udat_close(dateTimeFormat); + + if (U_FAILURE(status)) { + return NS_ERROR_FAILURE; + } + + return rv; +} + +/*static*/ +nsresult DateTimeFormat::GetCalendarSymbol(const Field aField, + const Style aStyle, + const PRExplodedTime* aExplodedTime, + nsAString& aStringOut) { + nsresult rv = Initialize(); + if (NS_FAILED(rv)) { + return rv; + } + + icu::DateFormatSymbols::DtWidthType widthType; + switch (aStyle) { + case Style::Wide: + widthType = icu::DateFormatSymbols::DtWidthType::WIDE; + break; + case Style::Abbreviated: + widthType = icu::DateFormatSymbols::DtWidthType::ABBREVIATED; + break; + } + + int32_t count; + UErrorCode status = U_ZERO_ERROR; + icu::Locale locale = icu::Locale::createCanonical(mLocale->get()); + + UDate udate = + static_cast<float>((PR_ImplodeTime(aExplodedTime) / PR_USEC_PER_MSEC)); + + nsAutoString timeZoneID; + BuildTimeZoneString(aExplodedTime->tm_params, timeZoneID); + std::unique_ptr<icu::TimeZone> timeZone( + icu::TimeZone::createTimeZone(timeZoneID.BeginReading())); + std::unique_ptr<icu::Calendar> cal( + icu::Calendar::createInstance(timeZone.release(), locale, status)); + if (U_FAILURE(status)) { + return NS_ERROR_UNEXPECTED; + } + + cal->setTime(udate, status); + if (U_FAILURE(status)) { + return NS_ERROR_UNEXPECTED; + } + + std::unique_ptr<icu::DateFormatSymbols> dfs( + icu::DateFormatSymbols::createForLocale(locale, status)); + if (U_FAILURE(status)) { + return NS_ERROR_UNEXPECTED; + } + + if (aField == Field::Month) { + int32_t month = cal->get(UCAL_MONTH, status); + if (U_FAILURE(status)) { + return NS_ERROR_UNEXPECTED; + } + const auto* months = dfs->getMonths( + count, icu::DateFormatSymbols::DtContextType::STANDALONE, widthType); + if (month < 0 || month >= count) { + return NS_ERROR_INVALID_ARG; + } + aStringOut.Assign(months[month].getBuffer(), months[month].length()); + } else if (aField == Field::Weekday) { + int32_t weekday = cal->get(UCAL_DAY_OF_WEEK, status); + if (U_FAILURE(status)) { + return NS_ERROR_UNEXPECTED; + } + const auto* weekdays = dfs->getWeekdays( + count, icu::DateFormatSymbols::DtContextType::STANDALONE, widthType); + if (weekday < 0 || weekday >= count) { + return NS_ERROR_INVALID_ARG; + } + aStringOut.Assign(weekdays[weekday].getBuffer(), + weekdays[weekday].length()); + } + + return NS_OK; +} + +// performs a locale sensitive date formatting operation on the UDate parameter +/*static*/ +nsresult DateTimeFormat::FormatUDateTime( + const nsDateFormatSelector aDateFormatSelector, + const nsTimeFormatSelector aTimeFormatSelector, const UDate aUDateTime, + const PRTimeParameters* aTimeParameters, nsAString& aStringOut) { + int32_t dateTimeLen = 0; + nsresult rv = NS_OK; + + // return, nothing to format + if (aDateFormatSelector == kDateFormatNone && + aTimeFormatSelector == kTimeFormatNone) { + aStringOut.Truncate(); + return NS_OK; + } + + // set up locale data + rv = Initialize(); + + if (NS_FAILED(rv)) { + return rv; + } + + UErrorCode status = U_ZERO_ERROR; + + nsAutoCString key; + key.AppendInt((int)aDateFormatSelector); + key.Append(':'); + key.AppendInt((int)aTimeFormatSelector); + if (aTimeParameters) { + key.Append(':'); + key.AppendInt(aTimeParameters->tp_gmt_offset); + key.Append(':'); + key.AppendInt(aTimeParameters->tp_dst_offset); + } + + if (mFormatCache && mFormatCache->Count() == kMaxCachedFormats) { + // Don't allow a pathological page to extend the cache unreasonably. + NS_WARNING("flushing UDateFormat cache"); + DeleteCache(); + } + if (!mFormatCache) { + mFormatCache = + new nsDataHashtable<nsCStringHashKey, UDateFormat*>(kMaxCachedFormats); + } + + UDateFormat*& dateTimeFormat = mFormatCache->GetOrInsert(key); + + if (!dateTimeFormat) { + // We didn't have a cached formatter for this key, so create one. + + int32_t dateFormatStyle; + switch (aDateFormatSelector) { + case kDateFormatLong: + dateFormatStyle = mozIOSPreferences::dateTimeFormatStyleLong; + break; + case kDateFormatShort: + dateFormatStyle = mozIOSPreferences::dateTimeFormatStyleShort; + break; + case kDateFormatNone: + dateFormatStyle = mozIOSPreferences::dateTimeFormatStyleNone; + break; + default: + NS_ERROR("Unknown nsDateFormatSelector"); + return NS_ERROR_ILLEGAL_VALUE; + } + + int32_t timeFormatStyle; + switch (aTimeFormatSelector) { + case kTimeFormatLong: + timeFormatStyle = mozIOSPreferences::dateTimeFormatStyleLong; + break; + case kTimeFormatShort: + timeFormatStyle = mozIOSPreferences::dateTimeFormatStyleShort; + break; + case kTimeFormatNone: + timeFormatStyle = mozIOSPreferences::dateTimeFormatStyleNone; + break; + default: + NS_ERROR("Unknown nsDateFormatSelector"); + return NS_ERROR_ILLEGAL_VALUE; + } + + nsAutoCString str; + rv = OSPreferences::GetInstance()->GetDateTimePattern( + dateFormatStyle, timeFormatStyle, nsDependentCString(mLocale->get()), + str); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString pattern = NS_ConvertUTF8toUTF16(str); + + if (aTimeParameters) { + nsAutoString timeZoneID; + BuildTimeZoneString(*aTimeParameters, timeZoneID); + + dateTimeFormat = + udat_open(UDAT_PATTERN, UDAT_PATTERN, mLocale->get(), + reinterpret_cast<const UChar*>(timeZoneID.BeginReading()), + timeZoneID.Length(), + reinterpret_cast<const UChar*>(pattern.BeginReading()), + pattern.Length(), &status); + } else { + dateTimeFormat = + udat_open(UDAT_PATTERN, UDAT_PATTERN, mLocale->get(), nullptr, -1, + reinterpret_cast<const UChar*>(pattern.BeginReading()), + pattern.Length(), &status); + } + } + + if (U_SUCCESS(status) && dateTimeFormat) { + aStringOut.SetLength(DATETIME_FORMAT_INITIAL_LEN); + dateTimeLen = + udat_format(dateTimeFormat, aUDateTime, + reinterpret_cast<UChar*>(aStringOut.BeginWriting()), + DATETIME_FORMAT_INITIAL_LEN, nullptr, &status); + aStringOut.SetLength(dateTimeLen); + + if (status == U_BUFFER_OVERFLOW_ERROR) { + status = U_ZERO_ERROR; + udat_format(dateTimeFormat, aUDateTime, + reinterpret_cast<UChar*>(aStringOut.BeginWriting()), + dateTimeLen, nullptr, &status); + } + } + + if (U_FAILURE(status)) { + rv = NS_ERROR_FAILURE; + } + + return rv; +} + +/*static*/ +void DateTimeFormat::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 DateTimeFormat::DeleteCache() { + if (mFormatCache) { + for (auto i = mFormatCache->Iter(); !i.Done(); i.Next()) { + udat_close(i.Data()); + } + delete mFormatCache; + mFormatCache = nullptr; + } +} + +/*static*/ +void DateTimeFormat::Shutdown() { + DeleteCache(); + if (mLocale) { + delete mLocale; + } +} + +} // namespace mozilla |