Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
#ifndef intl_components_DisplayNames_h_
#define intl_components_DisplayNames_h_
#include <string>
#include <string_view>
#include "unicode/udat.h"
#include "unicode/udatpg.h"
#include "unicode/uldnames.h"
#include "unicode/uloc.h"
#include "unicode/ucurr.h"
#include "mozilla/intl/Calendar.h"
#include "mozilla/intl/DateTimePatternGenerator.h"
#include "mozilla/intl/ICU4CGlue.h"
#include "mozilla/intl/Locale.h"
#include "mozilla/Buffer.h"
#include "mozilla/Casting.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Result.h"
#include "mozilla/Span.h"
#include "mozilla/TextUtils.h"
#include "mozilla/UniquePtr.h"
namespace mozilla::intl {
/**
* Provide more granular errors for DisplayNames rather than use the generic
* ICUError type. This helps with providing more actionable feedback for
* errors with input validation.
*
* This type can't be nested in the DisplayNames class because it needs the
* UnusedZero and HasFreeLSB definitions.
*/
enum class DisplayNamesError {
// Since we claim UnusedZero<DisplayNamesError>::value and
// HasFreeLSB<Error>::value == true below, we must only use positive,
// even enum values.
InternalError = 2,
OutOfMemory = 4,
InvalidOption = 6,
DuplicateVariantSubtag = 8,
InvalidLanguageTag = 10,
};
} // namespace mozilla::intl
namespace mozilla::detail {
// Ensure the efficient packing of the error types into the result. See
// ICUError.h and the ICUError comments for more information.
template <>
struct UnusedZero<intl::DisplayNamesError>
: UnusedZeroEnum<intl::DisplayNamesError> {};
template <>
struct HasFreeLSB<intl::DisplayNamesError> {
static constexpr bool value = true;
};
} // namespace mozilla::detail
namespace mozilla::intl {
// NOTE: The UTF-35 canonical "code" value for months and quarters are 1-based
// integers, so some of the following enums are 1-based for consistency with
// that. For simplicity, we make all of the following enums 1-based, but use
// `EnumToIndex` (see below) to convert to zero based if indexing into internal
// (non-ICU) tables.
/**
* Month choices for display names.
*/
enum class Month : uint8_t {
January = 1,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December,
// Some calendar systems feature a 13th month.
Undecimber
};
/**
* Quarter choices for display names.
*/
enum class Quarter : uint8_t {
Q1 = 1,
Q2,
Q3,
Q4,
};
/**
* Day period choices for display names.
*/
enum class DayPeriod : uint8_t {
AM = 1,
PM,
};
/**
* DateTimeField choices for display names.
*/
enum class DateTimeField : uint8_t {
Era = 1,
Year,
Quarter,
Month,
WeekOfYear,
Weekday,
Day,
DayPeriod,
Hour,
Minute,
Second,
TimeZoneName,
};
/**
* DisplayNames provide a way to get the localized names of various types of
* information such as the names of the day of the week, months, currency etc.
*
* This class backs SpiderMonkeys implementation of Intl.DisplayNames
*/
class DisplayNames final {
public:
/**
* The style of the display name, specified by the amount of space available
* for displaying the text.
*/
enum class Style {
Narrow,
Short,
Long,
// Note: Abbreviated is not part of ECMA-402, but it is available for
// internal Mozilla usage.
Abbreviated,
};
/**
* Use either standard or dialect names for the "Language" type.
*/
enum class LanguageDisplay {
Standard,
Dialect,
};
/**
* Determines the fallback behavior if no match is found.
*/
enum class Fallback {
// The buffer will contain an empty string.
None,
// The buffer will contain the code, but typically in a canonicalized form.
Code
};
/**
* These options correlate to the ECMA-402 DisplayNames options. The defaults
* values must match the default initialized values of ECMA-402. The type
* option is omitted as the C++ API relies on directly calling the
* DisplayNames::Get* methods.
*
*/
struct Options {
Style style = Style::Long;
LanguageDisplay languageDisplay = LanguageDisplay::Standard;
};
DisplayNames(ULocaleDisplayNames* aDisplayNames, Span<const char> aLocale,
Options aOptions)
: mOptions(aOptions), mULocaleDisplayNames(aDisplayNames) {
MOZ_ASSERT(aDisplayNames);
// Copy the span and ensure null termination.
mLocale = Buffer<char>(aLocale.size() + 1);
PodCopy(mLocale.begin(), aLocale.data(), aLocale.size());
mLocale[aLocale.size()] = '\0';
}
/**
* Initialize a new DisplayNames for the provided locale and using the
* provided options.
*
*/
static Result<UniquePtr<DisplayNames>, ICUError> TryCreate(
const char* aLocale, Options aOptions);
// Not copyable or movable
DisplayNames(const DisplayNames&) = delete;
DisplayNames& operator=(const DisplayNames&) = delete;
~DisplayNames();
/**
* Easily convert to a more specific DisplayNames error.
*/
DisplayNamesError ToError(ICUError aError) const;
/**
* Easily convert to a more specific DisplayNames error.
*/
DisplayNamesError ToError(Locale::CanonicalizationError aError) const;
private:
/**
* A helper function to handle the fallback behavior, where if there is a
* fallback the buffer is filled with the "code", often in canonicalized form.
*/
template <typename B, typename Fn>
static Result<Ok, DisplayNamesError> HandleFallback(B& aBuffer,
Fallback aFallback,
Fn aGetFallbackSpan) {
if (aBuffer.length() == 0 &&
aFallback == mozilla::intl::DisplayNames::Fallback::Code) {
if (!FillBuffer(aGetFallbackSpan(), aBuffer)) {
return Err(DisplayNamesError::OutOfMemory);
}
}
return Ok();
}
/**
* This is a specialized form of the FillBufferWithICUCall for DisplayNames.
* Different APIs report that no display name is found with different
* statuses. This method signals no display name was found by setting the
* buffer to 0.
*
* The display name APIs such as `uldn_scriptDisplayName`,
* `uloc_getDisplayScript`, and `uldn_regionDisplayName` report
* U_ILLEGAL_ARGUMENT_ERROR when no display name was found. In order to
* accomodate fallbacking, return an empty string in this case.
*/
template <typename B, typename F>
static ICUResult FillBufferWithICUDisplayNames(
B& aBuffer, UErrorCode aNoDisplayNameStatus, F aCallback) {
return FillBufferWithICUCall(
aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
int32_t res = aCallback(target, length, status);
if (*status == aNoDisplayNameStatus) {
*status = U_ZERO_ERROR;
res = 0;
}
return res;
});
}
/**
* An internal helper to compute the list of display names for various
* DateTime options.
*/
Result<Ok, DisplayNamesError> ComputeDateTimeDisplayNames(
UDateFormatSymbolType symbolType, mozilla::Span<const int32_t> indices,
Span<const char> aCalendar);
// The following are the stack-allocated sizes for various strings using the
// mozilla::Vector. The numbers should be large enough to fit the common
// cases, and when the strings are too large they will fall back to heap
// allocations.
// Fit BCP 47 locales such as "en-US", "zh-Hant". Locales can get quite long,
// but 32 should fit most smaller locales without a lot of extensions.
static constexpr size_t LocaleVecLength = 32;
// Fit calendar names such as "gregory", "buddhist", "islamic-civil".
// "islamic-umalqura" is 16 bytes + 1 for null termination, so round up to 32.
static constexpr size_t CalendarVecLength = 32;
/**
* Given an ASCII alpha, convert it to upper case.
*/
static inline char16_t AsciiAlphaToUpperCase(char16_t aCh) {
MOZ_ASSERT(IsAsciiAlpha(aCh));
return AsciiToUpperCase(aCh);
};
/**
* Attempt to use enums to safely index into an array.
*
* Note: The enums we support here are all defined starting from 1.
*/
template <typename T>
inline int32_t EnumToIndex(size_t aSize, T aEnum) {
size_t index = static_cast<size_t>(aEnum) - 1;
MOZ_RELEASE_ASSERT(index < aSize,
"Enum indexing mismatch for display names.");
return index;
}
/**
* Convert the month to a numeric code as a string.
*/
static Span<const char> ToCodeString(Month aMonth);
public:
/**
* Get the localized name of a language. Part of ECMA-402.
*
* Accepts:
* languageCode ["-" scriptCode] ["-" regionCode ] *("-" variant )
* Where the language code is:
* 1. A two letters ISO 639-1 language code
* 2. A three letters ISO 639-2 language code
*
* Examples:
* "es-ES" => "European Spanish" (en-US), "español de España" (es-ES)
* "zh-Hant" => "Traditional Chinese" (en-US), "chino tradicional" (es-ES)
*/
template <typename B>
Result<Ok, DisplayNamesError> GetLanguage(
B& aBuffer, Span<const char> aLanguage,
Fallback aFallback = Fallback::None) const {
static_assert(std::is_same<typename B::CharType, char16_t>::value);
mozilla::intl::Locale tag;
if (LocaleParser::TryParseBaseName(aLanguage, tag).isErr()) {
return Err(DisplayNamesError::InvalidOption);
}
{
// ICU always canonicalizes the input locale, but since we know that ICU's
// canonicalization is incomplete, we need to perform our own
// canonicalization to ensure consistent result.
auto result = tag.CanonicalizeBaseName();
if (result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
}
Vector<char, DisplayNames::LocaleVecLength> tagVec;
{
VectorToBufferAdaptor tagBuffer(tagVec);
auto result = tag.ToString(tagBuffer);
if (result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
if (!tagVec.append('\0')) {
// The tag should be null terminated.
return Err(DisplayNamesError::OutOfMemory);
}
}
auto result = FillBufferWithICUDisplayNames(
aBuffer, U_ILLEGAL_ARGUMENT_ERROR,
[&](UChar* target, int32_t length, UErrorCode* status) {
return uldn_localeDisplayName(mULocaleDisplayNames.GetConst(),
tagVec.begin(), target, length, status);
});
if (result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
return HandleFallback(aBuffer, aFallback, [&] {
// Remove the null terminator.
return Span(tagVec.begin(), tagVec.length() - 1);
});
};
/**
* Get the localized name of a region. Part of ECMA-402.
*
* Accepts:
* 1. an ISO-3166 two letters:
* 2. region code, or a three digits UN M49 Geographic Regions.
*
* Examples
* "US" => "United States" (en-US), "Estados Unidos", (es-ES)
* "158" => "Taiwan" (en-US), "Taiwán", (es-ES)
*/
template <typename B>
Result<Ok, DisplayNamesError> GetRegion(
B& aBuffer, Span<const char> aCode,
Fallback aFallback = Fallback::None) const {
static_assert(std::is_same<typename B::CharType, char16_t>::value);
mozilla::intl::RegionSubtag region;
if (!IsStructurallyValidRegionTag(aCode)) {
return Err(DisplayNamesError::InvalidOption);
}
region.Set(aCode);
mozilla::intl::Locale tag;
tag.SetLanguage("und");
tag.SetRegion(region);
{
// ICU always canonicalizes the input locale, but since we know that ICU's
// canonicalization is incomplete, we need to perform our own
// canonicalization to ensure consistent result.
auto result = tag.CanonicalizeBaseName();
if (result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
}
MOZ_ASSERT(tag.Region().Present());
// Note: ICU requires the region subtag to be in canonical case.
const mozilla::intl::RegionSubtag& canonicalRegion = tag.Region();
char regionChars[mozilla::intl::LanguageTagLimits::RegionLength + 1] = {};
std::copy_n(canonicalRegion.Span().data(), canonicalRegion.Length(),
regionChars);
auto result = FillBufferWithICUDisplayNames(
aBuffer, U_ILLEGAL_ARGUMENT_ERROR,
[&](UChar* chars, uint32_t size, UErrorCode* status) {
return uldn_regionDisplayName(
mULocaleDisplayNames.GetConst(), regionChars, chars,
AssertedCast<int32_t, uint32_t>(size), status);
});
if (result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
return HandleFallback(aBuffer, aFallback, [&] {
region.ToUpperCase();
return region.Span();
});
}
/**
* Get the localized name of a currency. Part of ECMA-402.
*
* Accepts:
* A 3-letter ISO 4217 currency code.
*
* Examples:
* "EUR" => "Euro" (en-US), "euro" (es_ES), "欧元", (zh)
* "JPY" => "Japanese Yen" (en-US), "yen" (es_ES), "日元", (zh)
*/
template <typename B>
Result<Ok, DisplayNamesError> GetCurrency(
B& aBuffer, Span<const char> aCurrency,
Fallback aFallback = Fallback::None) const {
static_assert(std::is_same<typename B::CharType, char16_t>::value);
if (aCurrency.size() != 3) {
return Err(DisplayNamesError::InvalidOption);
}
if (!mozilla::IsAsciiAlpha(aCurrency[0]) ||
!mozilla::IsAsciiAlpha(aCurrency[1]) ||
!mozilla::IsAsciiAlpha(aCurrency[2])) {
return Err(DisplayNamesError::InvalidOption);
}
// Normally this type of operation wouldn't be safe, but ASCII characters
// all take 1 byte in UTF-8 encoding, and can be zero padded to be valid
// UTF-16. Currency codes are all three ASCII letters.
// Normalize to upper case so we can easily detect the fallback case.
char16_t currency[] = {AsciiAlphaToUpperCase(aCurrency[0]),
AsciiAlphaToUpperCase(aCurrency[1]),
AsciiAlphaToUpperCase(aCurrency[2]), u'\0'};
UCurrNameStyle style;
switch (mOptions.style) {
case Style::Long:
style = UCURR_LONG_NAME;
break;
case Style::Abbreviated:
case Style::Short:
style = UCURR_SYMBOL_NAME;
break;
case Style::Narrow:
style = UCURR_NARROW_SYMBOL_NAME;
break;
}
int32_t length = 0;
UErrorCode status = U_ZERO_ERROR;
const char16_t* name = ucurr_getName(currency, IcuLocale(mLocale), style,
nullptr, &length, &status);
if (U_FAILURE(status)) {
return Err(DisplayNamesError::InternalError);
}
// No localized currency name was found when the error code is
// U_USING_DEFAULT_WARNING and the returned string is equal to the (upper
// case transformed) currency code. When `aFallback` is `Fallback::Code`,
// we don't have to perform any additional work, because ICU already
// returned the currency code in its normalized, upper case form.
if (aFallback == DisplayNames::Fallback::None &&
status == U_USING_DEFAULT_WARNING && length == 3 &&
std::u16string_view{name, 3} == std::u16string_view{currency, 3}) {
if (aBuffer.length() != 0) {
// Ensure an empty string is in the buffer when there is no fallback.
aBuffer.written(0);
}
return Ok();
}
if (!FillBuffer(Span(name, length), aBuffer)) {
return Err(DisplayNamesError::OutOfMemory);
}
return Ok();
}
/**
* Get the localized name of a script. Part of ECMA-402.
*
* Accepts:
* ECMA-402 expects the ISO-15924 four letters script code.
* e.g. "Latn"
*
* Examples:
* "Cher" => "Cherokee" (en-US), "cherokee" (es-ES)
* "Latn" => "Latin" (en-US), "latino" (es-ES)
*/
template <typename B>
Result<Ok, DisplayNamesError> GetScript(
B& aBuffer, Span<const char> aScript,
Fallback aFallback = Fallback::None) const {
static_assert(std::is_same<typename B::CharType, char16_t>::value);
mozilla::intl::ScriptSubtag script;
if (!IsStructurallyValidScriptTag(aScript)) {
return Err(DisplayNamesError::InvalidOption);
}
script.Set(aScript);
mozilla::intl::Locale tag;
tag.SetLanguage("und");
tag.SetScript(script);
{
// ICU always canonicalizes the input locale, but since we know that ICU's
// canonicalization is incomplete, we need to perform our own
// canonicalization to ensure consistent result.
auto result = tag.CanonicalizeBaseName();
if (result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
}
MOZ_ASSERT(tag.Script().Present());
mozilla::Vector<char, DisplayNames::LocaleVecLength> tagString;
VectorToBufferAdaptor buffer(tagString);
switch (mOptions.style) {
case Style::Long: {
// |uldn_scriptDisplayName| doesn't use the stand-alone form for script
// subtags, so we're using |uloc_getDisplayScript| instead. (This only
// applies to the long form.)
//
// |uloc_getDisplayScript| expects a full locale identifier as its
// input.
if (auto result = tag.ToString(buffer); result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
// Null terminate the tag string.
if (!tagString.append('\0')) {
return Err(DisplayNamesError::OutOfMemory);
}
auto result = FillBufferWithICUDisplayNames(
aBuffer, U_USING_DEFAULT_WARNING,
[&](UChar* target, int32_t length, UErrorCode* status) {
return uloc_getDisplayScript(tagString.begin(),
IcuLocale(mLocale), target, length,
status);
});
if (result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
break;
}
case Style::Abbreviated:
case Style::Short:
case Style::Narrow: {
// Note: ICU requires the script subtag to be in canonical case.
const mozilla::intl::ScriptSubtag& canonicalScript = tag.Script();
char scriptChars[mozilla::intl::LanguageTagLimits::ScriptLength + 1] =
{};
MOZ_ASSERT(canonicalScript.Length() <=
mozilla::intl::LanguageTagLimits::ScriptLength + 1);
std::copy_n(canonicalScript.Span().data(), canonicalScript.Length(),
scriptChars);
auto result = FillBufferWithICUDisplayNames(
aBuffer, U_ILLEGAL_ARGUMENT_ERROR,
[&](UChar* target, int32_t length, UErrorCode* status) {
return uldn_scriptDisplayName(mULocaleDisplayNames.GetConst(),
scriptChars, target, length,
status);
});
if (result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
break;
}
}
return HandleFallback(aBuffer, aFallback, [&] {
script.ToTitleCase();
return script.Span();
});
};
/**
* Get the localized name of a calendar.
* Part of Intl.DisplayNames V2. https://tc39.es/intl-displaynames-v2/
* Accepts:
* Unicode calendar key:
*/
template <typename B>
Result<Ok, DisplayNamesError> GetCalendar(
B& aBuffer, Span<const char> aCalendar,
Fallback aFallback = Fallback::None) const {
if (aCalendar.empty() || !IsAscii(aCalendar)) {
return Err(DisplayNamesError::InvalidOption);
}
if (LocaleParser::CanParseUnicodeExtensionType(aCalendar).isErr()) {
return Err(DisplayNamesError::InvalidOption);
}
// Convert into canonical case before searching for replacements.
Vector<char, DisplayNames::CalendarVecLength> lowerCaseCalendar;
for (size_t i = 0; i < aCalendar.size(); i++) {
if (!lowerCaseCalendar.append(AsciiToLowerCase(aCalendar[i]))) {
return Err(DisplayNamesError::OutOfMemory);
}
}
if (!lowerCaseCalendar.append('\0')) {
return Err(DisplayNamesError::OutOfMemory);
}
Span<const char> canonicalCalendar = mozilla::Span(
lowerCaseCalendar.begin(), lowerCaseCalendar.length() - 1);
// Search if there's a replacement for the Unicode calendar keyword.
{
Span<const char> key = mozilla::MakeStringSpan("ca");
Span<const char> type = canonicalCalendar;
if (const char* replacement =
mozilla::intl::Locale::ReplaceUnicodeExtensionType(key, type)) {
canonicalCalendar = MakeStringSpan(replacement);
}
}
// The input calendar name is user-controlled, so be extra cautious before
// passing arbitrarily large strings to ICU.
static constexpr size_t maximumCalendarLength = 100;
if (canonicalCalendar.size() <= maximumCalendarLength) {
// |uldn_keyValueDisplayName| expects old-style keyword values.
if (const char* legacyCalendar =
uloc_toLegacyType("calendar", canonicalCalendar.Elements())) {
auto result = FillBufferWithICUDisplayNames(
aBuffer, U_ILLEGAL_ARGUMENT_ERROR,
[&](UChar* chars, uint32_t size, UErrorCode* status) {
// |uldn_keyValueDisplayName| expects old-style keyword values.
return uldn_keyValueDisplayName(mULocaleDisplayNames.GetConst(),
"calendar", legacyCalendar, chars,
size, status);
});
if (result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
} else {
aBuffer.written(0);
}
} else {
aBuffer.written(0);
}
return HandleFallback(aBuffer, aFallback,
[&] { return canonicalCalendar; });
}
/**
* Get the localized name of a weekday. This is a MozExtension, and not
* currently part of ECMA-402.
*/
template <typename B>
Result<Ok, DisplayNamesError> GetWeekday(
B& aBuffer, Weekday aWeekday, Span<const char> aCalendar,
Fallback aFallback = Fallback::None) {
// SpiderMonkey static casts the enum, so ensure it is correctly in range.
MOZ_ASSERT(aWeekday >= Weekday::Monday && aWeekday <= Weekday::Sunday);
UDateFormatSymbolType symbolType;
switch (mOptions.style) {
case DisplayNames::Style::Long:
symbolType = UDAT_STANDALONE_WEEKDAYS;
break;
case DisplayNames::Style::Abbreviated:
// ICU "short" is CLDR "abbreviated" format.
symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
break;
case DisplayNames::Style::Short:
// ICU "shorter" is CLDR "short" format.
symbolType = UDAT_STANDALONE_SHORTER_WEEKDAYS;
break;
case DisplayNames::Style::Narrow:
symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
break;
}
static constexpr int32_t indices[] = {
UCAL_MONDAY, UCAL_TUESDAY, UCAL_WEDNESDAY, UCAL_THURSDAY,
UCAL_FRIDAY, UCAL_SATURDAY, UCAL_SUNDAY};
if (auto result = ComputeDateTimeDisplayNames(
symbolType, mozilla::Span(indices), aCalendar);
result.isErr()) {
return result.propagateErr();
}
MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices));
auto& name =
mDateTimeDisplayNames[EnumToIndex(std::size(indices), aWeekday)];
if (!FillBuffer(name.AsSpan(), aBuffer)) {
return Err(DisplayNamesError::OutOfMemory);
}
// There is no need to fallback, as invalid options are
// DisplayNamesError::InvalidOption.
return Ok();
}
/**
* Get the localized name of a month. This is a MozExtension, and not
* currently part of ECMA-402.
*/
template <typename B>
Result<Ok, DisplayNamesError> GetMonth(B& aBuffer, Month aMonth,
Span<const char> aCalendar,
Fallback aFallback = Fallback::None) {
// SpiderMonkey static casts the enum, so ensure it is correctly in range.
MOZ_ASSERT(aMonth >= Month::January && aMonth <= Month::Undecimber);
UDateFormatSymbolType symbolType;
switch (mOptions.style) {
case DisplayNames::Style::Long:
symbolType = UDAT_STANDALONE_MONTHS;
break;
case DisplayNames::Style::Abbreviated:
case DisplayNames::Style::Short:
symbolType = UDAT_STANDALONE_SHORT_MONTHS;
break;
case DisplayNames::Style::Narrow:
symbolType = UDAT_STANDALONE_NARROW_MONTHS;
break;
}
static constexpr int32_t indices[] = {
UCAL_JANUARY, UCAL_FEBRUARY, UCAL_MARCH, UCAL_APRIL,
UCAL_MAY, UCAL_JUNE, UCAL_JULY, UCAL_AUGUST,
UCAL_SEPTEMBER, UCAL_OCTOBER, UCAL_NOVEMBER, UCAL_DECEMBER,
UCAL_UNDECIMBER};
if (auto result = ComputeDateTimeDisplayNames(
symbolType, mozilla::Span(indices), aCalendar);
result.isErr()) {
return result.propagateErr();
}
MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices));
auto& name = mDateTimeDisplayNames[EnumToIndex(std::size(indices), aMonth)];
if (!FillBuffer(Span(name.AsSpan()), aBuffer)) {
return Err(DisplayNamesError::OutOfMemory);
}
return HandleFallback(aBuffer, aFallback,
[&] { return ToCodeString(aMonth); });
}
/**
* Get the localized name of a quarter. This is a MozExtension, and not
* currently part of ECMA-402.
*/
template <typename B>
Result<Ok, DisplayNamesError> GetQuarter(
B& aBuffer, Quarter aQuarter, Span<const char> aCalendar,
Fallback aFallback = Fallback::None) {
// SpiderMonkey static casts the enum, so ensure it is correctly in range.
MOZ_ASSERT(aQuarter >= Quarter::Q1 && aQuarter <= Quarter::Q4);
UDateFormatSymbolType symbolType;
switch (mOptions.style) {
case DisplayNames::Style::Long:
symbolType = UDAT_STANDALONE_QUARTERS;
break;
case DisplayNames::Style::Abbreviated:
case DisplayNames::Style::Short:
symbolType = UDAT_STANDALONE_SHORT_QUARTERS;
break;
case DisplayNames::Style::Narrow:
symbolType = UDAT_STANDALONE_NARROW_QUARTERS;
break;
}
// ICU doesn't provide an enum for quarters.
static constexpr int32_t indices[] = {0, 1, 2, 3};
if (auto result = ComputeDateTimeDisplayNames(
symbolType, mozilla::Span(indices), aCalendar);
result.isErr()) {
return result.propagateErr();
}
MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices));
auto& name =
mDateTimeDisplayNames[EnumToIndex(std::size(indices), aQuarter)];
if (!FillBuffer(Span(name.AsSpan()), aBuffer)) {
return Err(DisplayNamesError::OutOfMemory);
}
// There is no need to fallback, as invalid options are
// DisplayNamesError::InvalidOption.
return Ok();
}
/**
* Get the localized name of a day period. This is a MozExtension, and not
* currently part of ECMA-402.
*/
template <typename B>
Result<Ok, DisplayNamesError> GetDayPeriod(
B& aBuffer, DayPeriod aDayPeriod, Span<const char> aCalendar,
Fallback aFallback = Fallback::None) {
UDateFormatSymbolType symbolType = UDAT_AM_PMS;
static constexpr int32_t indices[] = {UCAL_AM, UCAL_PM};
if (auto result = ComputeDateTimeDisplayNames(
symbolType, mozilla::Span(indices), aCalendar);
result.isErr()) {
return result.propagateErr();
}
MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices));
auto& name =
mDateTimeDisplayNames[EnumToIndex(std::size(indices), aDayPeriod)];
if (!FillBuffer(name.AsSpan(), aBuffer)) {
return Err(DisplayNamesError::OutOfMemory);
}
// There is no need to fallback, as invalid options are
// DisplayNamesError::InvalidOption.
return Ok();
}
/**
* Get the localized name of a date time field.
* Part of Intl.DisplayNames V2. https://tc39.es/intl-displaynames-v2/
* Accepts:
* "era", "year", "quarter", "month", "weekOfYear", "weekday", "day",
* "dayPeriod", "hour", "minute", "second", "timeZoneName"
* Examples:
* "weekday" => "day of the week"
* "dayPeriod" => "AM/PM"
*/
template <typename B>
Result<Ok, DisplayNamesError> GetDateTimeField(
B& aBuffer, DateTimeField aField,
DateTimePatternGenerator& aDateTimePatternGen,
Fallback aFallback = Fallback::None) {
UDateTimePatternField field;
switch (aField) {
case DateTimeField::Era:
field = UDATPG_ERA_FIELD;
break;
case DateTimeField::Year:
field = UDATPG_YEAR_FIELD;
break;
case DateTimeField::Quarter:
field = UDATPG_QUARTER_FIELD;
break;
case DateTimeField::Month:
field = UDATPG_MONTH_FIELD;
break;
case DateTimeField::WeekOfYear:
field = UDATPG_WEEK_OF_YEAR_FIELD;
break;
case DateTimeField::Weekday:
field = UDATPG_WEEKDAY_FIELD;
break;
case DateTimeField::Day:
field = UDATPG_DAY_FIELD;
break;
case DateTimeField::DayPeriod:
field = UDATPG_DAYPERIOD_FIELD;
break;
case DateTimeField::Hour:
field = UDATPG_HOUR_FIELD;
break;
case DateTimeField::Minute:
field = UDATPG_MINUTE_FIELD;
break;
case DateTimeField::Second:
field = UDATPG_SECOND_FIELD;
break;
case DateTimeField::TimeZoneName:
field = UDATPG_ZONE_FIELD;
break;
}
UDateTimePGDisplayWidth width;
switch (mOptions.style) {
case DisplayNames::Style::Long:
width = UDATPG_WIDE;
break;
case DisplayNames::Style::Abbreviated:
case DisplayNames::Style::Short:
width = UDATPG_ABBREVIATED;
break;
case DisplayNames::Style::Narrow:
width = UDATPG_NARROW;
break;
}
auto result = FillBufferWithICUCall(
aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
return udatpg_getFieldDisplayName(
aDateTimePatternGen.GetUDateTimePatternGenerator(), field, width,
target, length, status);
});
if (result.isErr()) {
return Err(ToError(result.unwrapErr()));
}
// There is no need to fallback, as invalid options are
// DisplayNamesError::InvalidOption.
return Ok();
}
Options mOptions;
Buffer<char> mLocale;
Vector<Buffer<char16_t>> mDateTimeDisplayNames;
ICUPointer<ULocaleDisplayNames> mULocaleDisplayNames =
ICUPointer<ULocaleDisplayNames>(nullptr);
};
} // namespace mozilla::intl
#endif