summaryrefslogtreecommitdiffstats
path: root/intl/components/src/DisplayNames.h
diff options
context:
space:
mode:
Diffstat (limited to 'intl/components/src/DisplayNames.h')
-rw-r--r--intl/components/src/DisplayNames.h971
1 files changed, 971 insertions, 0 deletions
diff --git a/intl/components/src/DisplayNames.h b/intl/components/src/DisplayNames.h
new file mode 100644
index 0000000000..ae519f61ce
--- /dev/null
+++ b/intl/components/src/DisplayNames.h
@@ -0,0 +1,971 @@
+/* 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.
+ // https://en.wikipedia.org/wiki/Undecimber
+ 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
+ * https://tc39.es/ecma402/#intl-displaynames-objects
+ */
+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.
+ *
+ * https://tc39.es/ecma402/#intl-displaynames-objects
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames
+ */
+ 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.
+ *
+ * https://tc39.es/ecma402/#sec-Intl.DisplayNames
+ */
+ 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
+ * https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+ * 2. A three letters ISO 639-2 language code
+ * https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
+ *
+ * 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:
+ * https://www.iso.org/iso-3166-country-codes.html
+ * 2. region code, or a three digits UN M49 Geographic Regions.
+ * https://unstats.un.org/unsd/methodology/m49/
+ *
+ * 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.
+ * https://en.wikipedia.org/wiki/ISO_4217
+ *
+ * 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.
+ char16_t currency[] = {static_cast<char16_t>(aCurrency[0]),
+ static_cast<char16_t>(aCurrency[1]),
+ static_cast<char16_t>(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);
+ }
+
+ if (status == U_USING_DEFAULT_WARNING) {
+ // A resource bundle lookup returned a result from the root locale.
+ if (aFallback == DisplayNames::Fallback::Code) {
+ // Return the canonicalized input when no localized currency name was
+ // found. Canonical case for currency is upper case.
+ if (!aBuffer.reserve(3)) {
+ return Err(DisplayNamesError::OutOfMemory);
+ }
+ aBuffer.data()[0] = AsciiAlphaToUpperCase(currency[0]);
+ aBuffer.data()[1] = AsciiAlphaToUpperCase(currency[1]);
+ aBuffer.data()[2] = AsciiAlphaToUpperCase(currency[2]);
+ aBuffer.written(3);
+ } else 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.
+ * https://unicode.org/iso15924/iso15924-codes.html
+ * 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.)
+ //
+ // ICU bug: https://unicode-org.atlassian.net/browse/ICU-9301
+
+ // |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:
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#unicode_calendar_keys
+ */
+ 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