diff options
Diffstat (limited to 'js/src/builtin/Intl.js')
-rw-r--r-- | js/src/builtin/Intl.js | 3008 |
1 files changed, 3008 insertions, 0 deletions
diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js new file mode 100644 index 000000000..493062c1c --- /dev/null +++ b/js/src/builtin/Intl.js @@ -0,0 +1,3008 @@ +/* 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/. */ + +/* Portions Copyright Norbert Lindenberg 2011-2012. */ + +/*global JSMSG_INTL_OBJECT_NOT_INITED: false, JSMSG_INVALID_LOCALES_ELEMENT: false, + JSMSG_INVALID_LANGUAGE_TAG: false, JSMSG_INVALID_LOCALE_MATCHER: false, + JSMSG_INVALID_OPTION_VALUE: false, JSMSG_INVALID_DIGITS_VALUE: false, + JSMSG_INTL_OBJECT_REINITED: false, JSMSG_INVALID_CURRENCY_CODE: false, + JSMSG_UNDEFINED_CURRENCY: false, JSMSG_INVALID_TIME_ZONE: false, + JSMSG_DATE_NOT_FINITE: false, + intl_Collator_availableLocales: false, + intl_availableCollations: false, + intl_CompareStrings: false, + intl_NumberFormat_availableLocales: false, + intl_numberingSystem: false, + intl_FormatNumber: false, + intl_DateTimeFormat_availableLocales: false, + intl_availableCalendars: false, + intl_patternForSkeleton: false, + intl_FormatDateTime: false, +*/ + +/* + * The Intl module specified by standard ECMA-402, + * ECMAScript Internationalization API Specification. + */ + + +/********** Locales, Time Zones, and Currencies **********/ + + +/** + * Convert s to upper case, but limited to characters a-z. + * + * Spec: ECMAScript Internationalization API Specification, 6.1. + */ +function toASCIIUpperCase(s) { + assert(typeof s === "string", "toASCIIUpperCase"); + + // String.prototype.toUpperCase may map non-ASCII characters into ASCII, + // so go character by character (actually code unit by code unit, but + // since we only care about ASCII characters here, that's OK). + var result = ""; + for (var i = 0; i < s.length; i++) { + var c = callFunction(std_String_charCodeAt, s, i); + result += (0x61 <= c && c <= 0x7A) + ? callFunction(std_String_fromCharCode, null, c & ~0x20) + : s[i]; + } + return result; +} + +/** + * Holder object for encapsulating regexp instances. + * + * Regular expression instances should be created after the initialization of + * self-hosted global. + */ +var internalIntlRegExps = std_Object_create(null); +internalIntlRegExps.unicodeLocaleExtensionSequenceRE = null; +internalIntlRegExps.languageTagRE = null; +internalIntlRegExps.duplicateVariantRE = null; +internalIntlRegExps.duplicateSingletonRE = null; +internalIntlRegExps.isWellFormedCurrencyCodeRE = null; +internalIntlRegExps.currencyDigitsRE = null; + +/** + * Regular expression matching a "Unicode locale extension sequence", which the + * specification defines as: "any substring of a language tag that starts with + * a separator '-' and the singleton 'u' and includes the maximum sequence of + * following non-singleton subtags and their preceding '-' separators." + * + * Alternatively, this may be defined as: the components of a language tag that + * match the extension production in RFC 5646, where the singleton component is + * "u". + * + * Spec: ECMAScript Internationalization API Specification, 6.2.1. + */ +function getUnicodeLocaleExtensionSequenceRE() { + return internalIntlRegExps.unicodeLocaleExtensionSequenceRE || + (internalIntlRegExps.unicodeLocaleExtensionSequenceRE = + RegExpCreate("-u(?:-[a-z0-9]{2,8})+")); +} + + +/** + * Removes Unicode locale extension sequences from the given language tag. + */ +function removeUnicodeExtensions(locale) { + // A wholly-privateuse locale has no extension sequences. + if (callFunction(std_String_startsWith, locale, "x-")) + return locale; + + // Otherwise, split on "-x-" marking the start of any privateuse component. + // Replace Unicode locale extension sequences in the left half, and return + // the concatenation. + var pos = callFunction(std_String_indexOf, locale, "-x-"); + if (pos < 0) + pos = locale.length; + + var left = callFunction(String_substring, locale, 0, pos); + var right = callFunction(String_substring, locale, pos); + + var extensions; + var unicodeLocaleExtensionSequenceRE = getUnicodeLocaleExtensionSequenceRE(); + while ((extensions = regexp_exec_no_statics(unicodeLocaleExtensionSequenceRE, left)) !== null) { + left = StringReplaceString(left, extensions[0], ""); + unicodeLocaleExtensionSequenceRE.lastIndex = 0; + } + + var combined = left + right; + assert(IsStructurallyValidLanguageTag(combined), "recombination produced an invalid language tag"); + assert(function() { + var uindex = callFunction(std_String_indexOf, combined, "-u-"); + if (uindex < 0) + return true; + var xindex = callFunction(std_String_indexOf, combined, "-x-"); + return xindex > 0 && xindex < uindex; + }(), "recombination failed to remove all Unicode locale extension sequences"); + + return combined; +} + + +/** + * Regular expression defining BCP 47 language tags. + * + * Spec: RFC 5646 section 2.1. + */ +function getLanguageTagRE() { + if (internalIntlRegExps.languageTagRE) + return internalIntlRegExps.languageTagRE; + + // RFC 5234 section B.1 + // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + var ALPHA = "[a-zA-Z]"; + // DIGIT = %x30-39 + // ; 0-9 + var DIGIT = "[0-9]"; + + // RFC 5646 section 2.1 + // alphanum = (ALPHA / DIGIT) ; letters and numbers + var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; + // regular = "art-lojban" ; these tags match the 'langtag' + // / "cel-gaulish" ; production, but their subtags + // / "no-bok" ; are not extended language + // / "no-nyn" ; or variant subtags: their meaning + // / "zh-guoyu" ; is defined by their registration + // / "zh-hakka" ; and all of these are deprecated + // / "zh-min" ; in favor of a more modern + // / "zh-min-nan" ; subtag or sequence of subtags + // / "zh-xiang" + var regular = "(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)"; + // irregular = "en-GB-oed" ; irregular tags do not match + // / "i-ami" ; the 'langtag' production and + // / "i-bnn" ; would not otherwise be + // / "i-default" ; considered 'well-formed' + // / "i-enochian" ; These tags are all valid, + // / "i-hak" ; but most are deprecated + // / "i-klingon" ; in favor of more modern + // / "i-lux" ; subtags or subtag + // / "i-mingo" ; combination + // / "i-navajo" + // / "i-pwn" + // / "i-tao" + // / "i-tay" + // / "i-tsu" + // / "sgn-BE-FR" + // / "sgn-BE-NL" + // / "sgn-CH-DE" + var irregular = "(?:en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)"; + // grandfathered = irregular ; non-redundant tags registered + // / regular ; during the RFC 3066 era + var grandfathered = "(?:" + irregular + "|" + regular + ")"; + // privateuse = "x" 1*("-" (1*8alphanum)) + var privateuse = "(?:x(?:-[a-z0-9]{1,8})+)"; + // singleton = DIGIT ; 0 - 9 + // / %x41-57 ; A - W + // / %x59-5A ; Y - Z + // / %x61-77 ; a - w + // / %x79-7A ; y - z + var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])"; + // extension = singleton 1*("-" (2*8alphanum)) + var extension = "(?:" + singleton + "(?:-" + alphanum + "{2,8})+)"; + // variant = 5*8alphanum ; registered variants + // / (DIGIT 3alphanum) + var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))"; + // region = 2ALPHA ; ISO 3166-1 code + // / 3DIGIT ; UN M.49 code + var region = "(?:" + ALPHA + "{2}|" + DIGIT + "{3})"; + // script = 4ALPHA ; ISO 15924 code + var script = "(?:" + ALPHA + "{4})"; + // extlang = 3ALPHA ; selected ISO 639 codes + // *2("-" 3ALPHA) ; permanently reserved + var extlang = "(?:" + ALPHA + "{3}(?:-" + ALPHA + "{3}){0,2})"; + // language = 2*3ALPHA ; shortest ISO 639 code + // ["-" extlang] ; sometimes followed by + // ; extended language subtags + // / 4ALPHA ; or reserved for future use + // / 5*8ALPHA ; or registered language subtag + var language = "(?:" + ALPHA + "{2,3}(?:-" + extlang + ")?|" + ALPHA + "{4}|" + ALPHA + "{5,8})"; + // langtag = language + // ["-" script] + // ["-" region] + // *("-" variant) + // *("-" extension) + // ["-" privateuse] + var langtag = language + "(?:-" + script + ")?(?:-" + region + ")?(?:-" + + variant + ")*(?:-" + extension + ")*(?:-" + privateuse + ")?"; + // Language-Tag = langtag ; normal language tags + // / privateuse ; private use tag + // / grandfathered ; grandfathered tags + var languageTag = "^(?:" + langtag + "|" + privateuse + "|" + grandfathered + ")$"; + + // Language tags are case insensitive (RFC 5646 section 2.1.1). + return (internalIntlRegExps.languageTagRE = RegExpCreate(languageTag, "i")); +} + + +function getDuplicateVariantRE() { + if (internalIntlRegExps.duplicateVariantRE) + return internalIntlRegExps.duplicateVariantRE; + + // RFC 5234 section B.1 + // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + var ALPHA = "[a-zA-Z]"; + // DIGIT = %x30-39 + // ; 0-9 + var DIGIT = "[0-9]"; + + // RFC 5646 section 2.1 + // alphanum = (ALPHA / DIGIT) ; letters and numbers + var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; + // variant = 5*8alphanum ; registered variants + // / (DIGIT 3alphanum) + var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))"; + + // Match a langtag that contains a duplicate variant. + var duplicateVariant = + // Match everything in a langtag prior to any variants, and maybe some + // of the variants as well (which makes this pattern inefficient but + // not wrong, for our purposes); + "(?:" + alphanum + "{2,8}-)+" + + // a variant, parenthesised so that we can refer back to it later; + "(" + variant + ")-" + + // zero or more subtags at least two characters long (thus stopping + // before extension and privateuse components); + "(?:" + alphanum + "{2,8}-)*" + + // and the same variant again + "\\1" + + // ...but not followed by any characters that would turn it into a + // different subtag. + "(?!" + alphanum + ")"; + + // Language tags are case insensitive (RFC 5646 section 2.1.1). Using + // character classes covering both upper- and lower-case characters nearly + // addresses this -- but for the possibility of variant repetition with + // differing case, e.g. "en-variant-Variant". Use a case-insensitive + // regular expression to address this. (Note that there's no worry about + // case transformation accepting invalid characters here: users have + // already verified the string is alphanumeric Latin plus "-".) + return (internalIntlRegExps.duplicateVariantRE = RegExpCreate(duplicateVariant, "i")); +} + + +function getDuplicateSingletonRE() { + if (internalIntlRegExps.duplicateSingletonRE) + return internalIntlRegExps.duplicateSingletonRE; + + // RFC 5234 section B.1 + // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + var ALPHA = "[a-zA-Z]"; + // DIGIT = %x30-39 + // ; 0-9 + var DIGIT = "[0-9]"; + + // RFC 5646 section 2.1 + // alphanum = (ALPHA / DIGIT) ; letters and numbers + var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; + // singleton = DIGIT ; 0 - 9 + // / %x41-57 ; A - W + // / %x59-5A ; Y - Z + // / %x61-77 ; a - w + // / %x79-7A ; y - z + var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])"; + + // Match a langtag that contains a duplicate singleton. + var duplicateSingleton = + // Match a singleton subtag, parenthesised so that we can refer back to + // it later; + "-(" + singleton + ")-" + + // then zero or more subtags; + "(?:" + alphanum + "+-)*" + + // and the same singleton again + "\\1" + + // ...but not followed by any characters that would turn it into a + // different subtag. + "(?!" + alphanum + ")"; + + // Language tags are case insensitive (RFC 5646 section 2.1.1). Using + // character classes covering both upper- and lower-case characters nearly + // addresses this -- but for the possibility of singleton repetition with + // differing case, e.g. "en-u-foo-U-foo". Use a case-insensitive regular + // expression to address this. (Note that there's no worry about case + // transformation accepting invalid characters here: users have already + // verified the string is alphanumeric Latin plus "-".) + return (internalIntlRegExps.duplicateSingletonRE = RegExpCreate(duplicateSingleton, "i")); +} + + +/** + * Verifies that the given string is a well-formed BCP 47 language tag + * with no duplicate variant or singleton subtags. + * + * Spec: ECMAScript Internationalization API Specification, 6.2.2. + */ +function IsStructurallyValidLanguageTag(locale) { + assert(typeof locale === "string", "IsStructurallyValidLanguageTag"); + var languageTagRE = getLanguageTagRE(); + if (!regexp_test_no_statics(languageTagRE, locale)) + return false; + + // Before checking for duplicate variant or singleton subtags with + // regular expressions, we have to get private use subtag sequences + // out of the picture. + if (callFunction(std_String_startsWith, locale, "x-")) + return true; + var pos = callFunction(std_String_indexOf, locale, "-x-"); + if (pos !== -1) + locale = callFunction(String_substring, locale, 0, pos); + + // Check for duplicate variant or singleton subtags. + var duplicateVariantRE = getDuplicateVariantRE(); + var duplicateSingletonRE = getDuplicateSingletonRE(); + return !regexp_test_no_statics(duplicateVariantRE, locale) && + !regexp_test_no_statics(duplicateSingletonRE, locale); +} + + +/** + * Canonicalizes the given structurally valid BCP 47 language tag, including + * regularized case of subtags. For example, the language tag + * Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE, where + * + * Zh ; 2*3ALPHA + * -NAN ; ["-" extlang] + * -haNS ; ["-" script] + * -bu ; ["-" region] + * -variant2 ; *("-" variant) + * -Variant1 + * -u-ca-chinese ; *("-" extension) + * -t-Zh-laTN + * -x-PRIVATE ; ["-" privateuse] + * + * becomes nan-Hans-mm-variant2-variant1-t-zh-latn-u-ca-chinese-x-private + * + * Spec: ECMAScript Internationalization API Specification, 6.2.3. + * Spec: RFC 5646, section 4.5. + */ +function CanonicalizeLanguageTag(locale) { + assert(IsStructurallyValidLanguageTag(locale), "CanonicalizeLanguageTag"); + + // The input + // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE" + // will be used throughout this method to illustrate how it works. + + // Language tags are compared and processed case-insensitively, so + // technically it's not necessary to adjust case. But for easier processing, + // and because the canonical form for most subtags is lower case, we start + // with lower case for all. + // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE" -> + // "zh-nan-hans-bu-variant2-variant1-u-ca-chinese-t-zh-latn-x-private" + locale = callFunction(std_String_toLowerCase, locale); + + // Handle mappings for complete tags. + if (callFunction(std_Object_hasOwnProperty, langTagMappings, locale)) + return langTagMappings[locale]; + + var subtags = StringSplitString(ToString(locale), "-"); + var i = 0; + + // Handle the standard part: All subtags before the first singleton or "x". + // "zh-nan-hans-bu-variant2-variant1" + while (i < subtags.length) { + var subtag = subtags[i]; + + // If we reach the start of an extension sequence or private use part, + // we're done with this loop. We have to check for i > 0 because for + // irregular language tags, such as i-klingon, the single-character + // subtag "i" is not the start of an extension sequence. + // In the example, we break at "u". + if (subtag.length === 1 && (i > 0 || subtag === "x")) + break; + + if (i !== 0) { + if (subtag.length === 4) { + // 4-character subtags that are not in initial position are + // script codes; their first character needs to be capitalized. + // "hans" -> "Hans" + subtag = callFunction(std_String_toUpperCase, subtag[0]) + + callFunction(String_substring, subtag, 1); + } else if (subtag.length === 2) { + // 2-character subtags that are not in initial position are + // region codes; they need to be upper case. "bu" -> "BU" + subtag = callFunction(std_String_toUpperCase, subtag); + } + } + if (callFunction(std_Object_hasOwnProperty, langSubtagMappings, subtag)) { + // Replace deprecated subtags with their preferred values. + // "BU" -> "MM" + // This has to come after we capitalize region codes because + // otherwise some language and region codes could be confused. + // For example, "in" is an obsolete language code for Indonesian, + // but "IN" is the country code for India. + // Note that the script generating langSubtagMappings makes sure + // that no regular subtag mapping will replace an extlang code. + subtag = langSubtagMappings[subtag]; + } else if (callFunction(std_Object_hasOwnProperty, extlangMappings, subtag)) { + // Replace deprecated extlang subtags with their preferred values, + // and remove the preceding subtag if it's a redundant prefix. + // "zh-nan" -> "nan" + // Note that the script generating extlangMappings makes sure that + // no extlang mapping will replace a normal language code. + subtag = extlangMappings[subtag].preferred; + if (i === 1 && extlangMappings[subtag].prefix === subtags[0]) { + callFunction(std_Array_shift, subtags); + i--; + } + } + subtags[i] = subtag; + i++; + } + var normal = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, 0, i), "-"); + + // Extension sequences are sorted by their singleton characters. + // "u-ca-chinese-t-zh-latn" -> "t-zh-latn-u-ca-chinese" + var extensions = new List(); + while (i < subtags.length && subtags[i] !== "x") { + var extensionStart = i; + i++; + while (i < subtags.length && subtags[i].length > 1) + i++; + var extension = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, extensionStart, i), "-"); + callFunction(std_Array_push, extensions, extension); + } + callFunction(std_Array_sort, extensions); + + // Private use sequences are left as is. "x-private" + var privateUse = ""; + if (i < subtags.length) + privateUse = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, i), "-"); + + // Put everything back together. + var canonical = normal; + if (extensions.length > 0) + canonical += "-" + callFunction(std_Array_join, extensions, "-"); + if (privateUse.length > 0) { + // Be careful of a Language-Tag that is entirely privateuse. + if (canonical.length > 0) + canonical += "-" + privateUse; + else + canonical = privateUse; + } + + return canonical; +} + + +function localeContainsNoUnicodeExtensions(locale) { + // No "-u-", no possible Unicode extension. + if (callFunction(std_String_indexOf, locale, "-u-") === -1) + return true; + + // "-u-" within privateuse also isn't one. + if (callFunction(std_String_indexOf, locale, "-u-") > callFunction(std_String_indexOf, locale, "-x-")) + return true; + + // An entirely-privateuse tag doesn't contain extensions. + if (callFunction(std_String_startsWith, locale, "x-")) + return true; + + // Otherwise, we have a Unicode extension sequence. + return false; +} + + +// The last-ditch locale is used if none of the available locales satisfies a +// request. "en-GB" is used based on the assumptions that English is the most +// common second language, that both en-GB and en-US are normally available in +// an implementation, and that en-GB is more representative of the English used +// in other locales. +function lastDitchLocale() { + // Per bug 1177929, strings don't clone out of self-hosted code as atoms, + // breaking IonBuilder::constant. Put this in a function for now. + return "en-GB"; +} + + +// Certain old, commonly-used language tags that lack a script, are expected to +// nonetheless imply one. This object maps these old-style tags to modern +// equivalents. +var oldStyleLanguageTagMappings = { + "pa-PK": "pa-Arab-PK", + "zh-CN": "zh-Hans-CN", + "zh-HK": "zh-Hant-HK", + "zh-SG": "zh-Hans-SG", + "zh-TW": "zh-Hant-TW", +}; + + +var localeCandidateCache = { + runtimeDefaultLocale: undefined, + candidateDefaultLocale: undefined, +}; + + +var localeCache = { + runtimeDefaultLocale: undefined, + defaultLocale: undefined, +}; + + +/** + * Compute the candidate default locale: the locale *requested* to be used as + * the default locale. We'll use it if and only if ICU provides support (maybe + * fallback support, e.g. supporting "de-ZA" through "de" support implied by a + * "de-DE" locale). + */ +function DefaultLocaleIgnoringAvailableLocales() { + const runtimeDefaultLocale = RuntimeDefaultLocale(); + if (runtimeDefaultLocale === localeCandidateCache.runtimeDefaultLocale) + return localeCandidateCache.candidateDefaultLocale; + + // If we didn't get a cache hit, compute the candidate default locale and + // cache it. Fall back on the last-ditch locale when necessary. + var candidate; + if (!IsStructurallyValidLanguageTag(runtimeDefaultLocale)) { + candidate = lastDitchLocale(); + } else { + candidate = CanonicalizeLanguageTag(runtimeDefaultLocale); + + // The default locale must be in [[availableLocales]], and that list + // must not contain any locales with Unicode extension sequences, so + // remove any present in the candidate. + candidate = removeUnicodeExtensions(candidate); + + if (callFunction(std_Object_hasOwnProperty, oldStyleLanguageTagMappings, candidate)) + candidate = oldStyleLanguageTagMappings[candidate]; + } + + // Cache the candidate locale until the runtime default locale changes. + localeCandidateCache.candidateDefaultLocale = candidate; + localeCandidateCache.runtimeDefaultLocale = runtimeDefaultLocale; + + assert(IsStructurallyValidLanguageTag(candidate), + "the candidate must be structurally valid"); + assert(localeContainsNoUnicodeExtensions(candidate), + "the candidate must not contain a Unicode extension sequence"); + + return candidate; +} + + +/** + * Returns the BCP 47 language tag for the host environment's current locale. + * + * Spec: ECMAScript Internationalization API Specification, 6.2.4. + */ +function DefaultLocale() { + const runtimeDefaultLocale = RuntimeDefaultLocale(); + if (runtimeDefaultLocale === localeCache.runtimeDefaultLocale) + return localeCache.defaultLocale; + + // If we didn't have a cache hit, compute the candidate default locale. + // Then use it as the actual default locale if ICU supports that locale + // (perhaps via fallback). Otherwise use the last-ditch locale. + var candidate = DefaultLocaleIgnoringAvailableLocales(); + var locale; + if (BestAvailableLocaleIgnoringDefault(callFunction(collatorInternalProperties.availableLocales, + collatorInternalProperties), + candidate) && + BestAvailableLocaleIgnoringDefault(callFunction(numberFormatInternalProperties.availableLocales, + numberFormatInternalProperties), + candidate) && + BestAvailableLocaleIgnoringDefault(callFunction(dateTimeFormatInternalProperties.availableLocales, + dateTimeFormatInternalProperties), + candidate)) + { + locale = candidate; + } else { + locale = lastDitchLocale(); + } + + assert(IsStructurallyValidLanguageTag(locale), + "the computed default locale must be structurally valid"); + assert(locale === CanonicalizeLanguageTag(locale), + "the computed default locale must be canonical"); + assert(localeContainsNoUnicodeExtensions(locale), + "the computed default locale must not contain a Unicode extension sequence"); + + localeCache.defaultLocale = locale; + localeCache.runtimeDefaultLocale = runtimeDefaultLocale; + + return locale; +} + + +/** + * Verifies that the given string is a well-formed ISO 4217 currency code. + * + * Spec: ECMAScript Internationalization API Specification, 6.3.1. + */ +function getIsWellFormedCurrencyCodeRE() { + return internalIntlRegExps.isWellFormedCurrencyCodeRE || + (internalIntlRegExps.isWellFormedCurrencyCodeRE = RegExpCreate("[^A-Z]")); +} +function IsWellFormedCurrencyCode(currency) { + var c = ToString(currency); + var normalized = toASCIIUpperCase(c); + if (normalized.length !== 3) + return false; + return !regexp_test_no_statics(getIsWellFormedCurrencyCodeRE(), normalized); +} + + +var timeZoneCache = { + icuDefaultTimeZone: undefined, + defaultTimeZone: undefined, +}; + + +/** + * 6.4.2 CanonicalizeTimeZoneName ( timeZone ) + * + * Canonicalizes the given IANA time zone name. + * + * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3 + */ +function CanonicalizeTimeZoneName(timeZone) { + assert(typeof timeZone === "string", "CanonicalizeTimeZoneName"); + + // Step 1. (Not applicable, the input is already a valid IANA time zone.) + assert(timeZone !== "Etc/Unknown", "Invalid time zone"); + assert(timeZone === intl_IsValidTimeZoneName(timeZone), "Time zone name not normalized"); + + // Step 2. + var ianaTimeZone = intl_canonicalizeTimeZone(timeZone); + assert(ianaTimeZone !== "Etc/Unknown", "Invalid canonical time zone"); + assert(ianaTimeZone === intl_IsValidTimeZoneName(ianaTimeZone), "Unsupported canonical time zone"); + + // Step 3. + if (ianaTimeZone === "Etc/UTC" || ianaTimeZone === "Etc/GMT") { + // ICU/CLDR canonicalizes Etc/UCT to Etc/GMT, but following IANA and + // ECMA-402 to the letter means Etc/UCT is a separate time zone. + if (timeZone === "Etc/UCT" || timeZone === "UCT") + ianaTimeZone = "Etc/UCT"; + else + ianaTimeZone = "UTC"; + } + + // Step 4. + return ianaTimeZone; +} + + +/** + * 6.4.3 DefaultTimeZone () + * + * Returns the IANA time zone name for the host environment's current time zone. + * + * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3 + */ +function DefaultTimeZone() { + const icuDefaultTimeZone = intl_defaultTimeZone(); + if (timeZoneCache.icuDefaultTimeZone === icuDefaultTimeZone) + return timeZoneCache.defaultTimeZone; + + // Verify that the current ICU time zone is a valid ECMA-402 time zone. + var timeZone = intl_IsValidTimeZoneName(icuDefaultTimeZone); + if (timeZone === null) { + // Before defaulting to "UTC", try to represent the default time zone + // using the Etc/GMT + offset format. This format only accepts full + // hour offsets. + const msPerHour = 60 * 60 * 1000; + var offset = intl_defaultTimeZoneOffset(); + assert(offset === (offset | 0), + "milliseconds offset shouldn't be able to exceed int32_t range"); + var offsetHours = offset / msPerHour, offsetHoursFraction = offset % msPerHour; + if (offsetHoursFraction === 0) { + // Etc/GMT + offset uses POSIX-style signs, i.e. a positive offset + // means a location west of GMT. + timeZone = "Etc/GMT" + (offsetHours < 0 ? "+" : "-") + std_Math_abs(offsetHours); + + // Check if the fallback is valid. + timeZone = intl_IsValidTimeZoneName(timeZone); + } + + // Fallback to "UTC" if everything else fails. + if (timeZone === null) + timeZone = "UTC"; + } + + // Canonicalize the ICU time zone, e.g. change Etc/UTC to UTC. + var defaultTimeZone = CanonicalizeTimeZoneName(timeZone); + + timeZoneCache.defaultTimeZone = defaultTimeZone; + timeZoneCache.icuDefaultTimeZone = icuDefaultTimeZone; + + return defaultTimeZone; +} + + +/********** Locale and Parameter Negotiation **********/ + +/** + * Add old-style language tags without script code for locales that in current + * usage would include a script subtag. Also add an entry for the last-ditch + * locale, in case ICU doesn't directly support it (but does support it through + * fallback, e.g. supporting "en-GB" indirectly using "en" support). + */ +function addSpecialMissingLanguageTags(availableLocales) { + // Certain old-style language tags lack a script code, but in current usage + // they *would* include a script code. Map these over to modern forms. + var oldStyleLocales = std_Object_getOwnPropertyNames(oldStyleLanguageTagMappings); + for (var i = 0; i < oldStyleLocales.length; i++) { + var oldStyleLocale = oldStyleLocales[i]; + if (availableLocales[oldStyleLanguageTagMappings[oldStyleLocale]]) + availableLocales[oldStyleLocale] = true; + } + + // Also forcibly provide the last-ditch locale. + var lastDitch = lastDitchLocale(); + assert(lastDitch === "en-GB" && availableLocales["en"], + "shouldn't be a need to add every locale implied by the last-" + + "ditch locale, merely just the last-ditch locale"); + availableLocales[lastDitch] = true; +} + + +/** + * Canonicalizes a locale list. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.1. + */ +function CanonicalizeLocaleList(locales) { + if (locales === undefined) + return new List(); + var seen = new List(); + if (typeof locales === "string") + locales = [locales]; + var O = ToObject(locales); + var len = ToLength(O.length); + var k = 0; + while (k < len) { + // Don't call ToString(k) - SpiderMonkey is faster with integers. + var kPresent = HasProperty(O, k); + if (kPresent) { + var kValue = O[k]; + if (!(typeof kValue === "string" || IsObject(kValue))) + ThrowTypeError(JSMSG_INVALID_LOCALES_ELEMENT); + var tag = ToString(kValue); + if (!IsStructurallyValidLanguageTag(tag)) + ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, tag); + tag = CanonicalizeLanguageTag(tag); + if (callFunction(ArrayIndexOf, seen, tag) === -1) + callFunction(std_Array_push, seen, tag); + } + k++; + } + return seen; +} + + +function BestAvailableLocaleHelper(availableLocales, locale, considerDefaultLocale) { + assert(IsStructurallyValidLanguageTag(locale), "invalid BestAvailableLocale locale structure"); + assert(locale === CanonicalizeLanguageTag(locale), "non-canonical BestAvailableLocale locale"); + assert(localeContainsNoUnicodeExtensions(locale), "locale must contain no Unicode extensions"); + + // In the spec, [[availableLocales]] is formally a list of all available + // locales. But in our implementation, it's an *incomplete* list, not + // necessarily including the default locale (and all locales implied by it, + // e.g. "de" implied by "de-CH"), if that locale isn't in every + // [[availableLocales]] list (because that locale is supported through + // fallback, e.g. "de-CH" supported through "de"). + // + // If we're considering the default locale, augment the spec loop with + // additional checks to also test whether the current prefix is a prefix of + // the default locale. + + var defaultLocale; + if (considerDefaultLocale) + defaultLocale = DefaultLocale(); + + var candidate = locale; + while (true) { + if (availableLocales[candidate]) + return candidate; + + if (considerDefaultLocale && candidate.length <= defaultLocale.length) { + if (candidate === defaultLocale) + return candidate; + if (callFunction(std_String_startsWith, defaultLocale, candidate + "-")) + return candidate; + } + + var pos = callFunction(std_String_lastIndexOf, candidate, "-"); + if (pos === -1) + return undefined; + + if (pos >= 2 && candidate[pos - 2] === "-") + pos -= 2; + + candidate = callFunction(String_substring, candidate, 0, pos); + } +} + + +/** + * Compares a BCP 47 language tag against the locales in availableLocales + * and returns the best available match. Uses the fallback + * mechanism of RFC 4647, section 3.4. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.2. + * Spec: RFC 4647, section 3.4. + */ +function BestAvailableLocale(availableLocales, locale) { + return BestAvailableLocaleHelper(availableLocales, locale, true); +} + + +/** + * Identical to BestAvailableLocale, but does not consider the default locale + * during computation. + */ +function BestAvailableLocaleIgnoringDefault(availableLocales, locale) { + return BestAvailableLocaleHelper(availableLocales, locale, false); +} + + +/** + * Compares a BCP 47 language priority list against the set of locales in + * availableLocales and determines the best available language to meet the + * request. Options specified through Unicode extension subsequences are + * ignored in the lookup, but information about such subsequences is returned + * separately. + * + * This variant is based on the Lookup algorithm of RFC 4647 section 3.4. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.3. + * Spec: RFC 4647, section 3.4. + */ +function LookupMatcher(availableLocales, requestedLocales) { + var i = 0; + var len = requestedLocales.length; + var availableLocale; + var locale, noExtensionsLocale; + while (i < len && availableLocale === undefined) { + locale = requestedLocales[i]; + noExtensionsLocale = removeUnicodeExtensions(locale); + availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale); + i++; + } + + var result = new Record(); + if (availableLocale !== undefined) { + result.locale = availableLocale; + if (locale !== noExtensionsLocale) { + var unicodeLocaleExtensionSequenceRE = getUnicodeLocaleExtensionSequenceRE(); + var extensionMatch = regexp_exec_no_statics(unicodeLocaleExtensionSequenceRE, locale); + var extension = extensionMatch[0]; + var extensionIndex = extensionMatch.index; + result.extension = extension; + result.extensionIndex = extensionIndex; + } + } else { + result.locale = DefaultLocale(); + } + return result; +} + + +/** + * Compares a BCP 47 language priority list against the set of locales in + * availableLocales and determines the best available language to meet the + * request. Options specified through Unicode extension subsequences are + * ignored in the lookup, but information about such subsequences is returned + * separately. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.4. + */ +function BestFitMatcher(availableLocales, requestedLocales) { + // this implementation doesn't have anything better + return LookupMatcher(availableLocales, requestedLocales); +} + + +/** + * Compares a BCP 47 language priority list against availableLocales and + * determines the best available language to meet the request. Options specified + * through Unicode extension subsequences are negotiated separately, taking the + * caller's relevant extensions and locale data as well as client-provided + * options into consideration. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.5. + */ +function ResolveLocale(availableLocales, requestedLocales, options, relevantExtensionKeys, localeData) { + /*jshint laxbreak: true */ + + // Steps 1-3. + var matcher = options.localeMatcher; + var r = (matcher === "lookup") + ? LookupMatcher(availableLocales, requestedLocales) + : BestFitMatcher(availableLocales, requestedLocales); + + // Step 4. + var foundLocale = r.locale; + + // Step 5.a. + var extension = r.extension; + var extensionIndex, extensionSubtags, extensionSubtagsLength; + + // Step 5. + if (extension !== undefined) { + // Step 5.b. + extensionIndex = r.extensionIndex; + + // Steps 5.d-e. + extensionSubtags = StringSplitString(ToString(extension), "-"); + extensionSubtagsLength = extensionSubtags.length; + } + + // Steps 6-7. + var result = new Record(); + result.dataLocale = foundLocale; + + // Step 8. + var supportedExtension = "-u"; + + // Steps 9-11. + var i = 0; + var len = relevantExtensionKeys.length; + while (i < len) { + // Steps 11.a-c. + var key = relevantExtensionKeys[i]; + + // In this implementation, localeData is a function, not an object. + var foundLocaleData = localeData(foundLocale); + var keyLocaleData = foundLocaleData[key]; + + // Locale data provides default value. + // Step 11.d. + var value = keyLocaleData[0]; + + // Locale tag may override. + + // Step 11.e. + var supportedExtensionAddition = ""; + + // Step 11.f is implemented by Utilities.js. + + var valuePos; + + // Step 11.g. + if (extensionSubtags !== undefined) { + // Step 11.g.i. + var keyPos = callFunction(ArrayIndexOf, extensionSubtags, key); + + // Step 11.g.ii. + if (keyPos !== -1) { + // Step 11.g.ii.1. + if (keyPos + 1 < extensionSubtagsLength && + extensionSubtags[keyPos + 1].length > 2) + { + // Step 11.g.ii.1.a. + var requestedValue = extensionSubtags[keyPos + 1]; + + // Step 11.g.ii.1.b. + valuePos = callFunction(ArrayIndexOf, keyLocaleData, requestedValue); + + // Step 11.g.ii.1.c. + if (valuePos !== -1) { + value = requestedValue; + supportedExtensionAddition = "-" + key + "-" + value; + } + } else { + // Step 11.g.ii.2. + + // According to the LDML spec, if there's no type value, + // and true is an allowed value, it's used. + + // Step 11.g.ii.2.a. + valuePos = callFunction(ArrayIndexOf, keyLocaleData, "true"); + + // Step 11.g.ii.2.b. + if (valuePos !== -1) + value = "true"; + } + } + } + + // Options override all. + + // Step 11.h.i. + var optionsValue = options[key]; + + // Step 11.h, 11.h.ii. + if (optionsValue !== undefined && + callFunction(ArrayIndexOf, keyLocaleData, optionsValue) !== -1) + { + // Step 11.h.ii.1. + if (optionsValue !== value) { + value = optionsValue; + supportedExtensionAddition = ""; + } + } + + // Steps 11.i-k. + result[key] = value; + supportedExtension += supportedExtensionAddition; + i++; + } + + // Step 12. + if (supportedExtension.length > 2) { + var preExtension = callFunction(String_substring, foundLocale, 0, extensionIndex); + var postExtension = callFunction(String_substring, foundLocale, extensionIndex); + foundLocale = preExtension + supportedExtension + postExtension; + } + + // Steps 13-14. + result.locale = foundLocale; + return result; +} + + +/** + * Returns the subset of requestedLocales for which availableLocales has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.6. + */ +function LookupSupportedLocales(availableLocales, requestedLocales) { + // Steps 1-2. + var len = requestedLocales.length; + var subset = new List(); + + // Steps 3-4. + var k = 0; + while (k < len) { + // Steps 4.a-b. + var locale = requestedLocales[k]; + var noExtensionsLocale = removeUnicodeExtensions(locale); + + // Step 4.c-d. + var availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale); + if (availableLocale !== undefined) + callFunction(std_Array_push, subset, locale); + + // Step 4.e. + k++; + } + + // Steps 5-6. + return callFunction(std_Array_slice, subset, 0); +} + + +/** + * Returns the subset of requestedLocales for which availableLocales has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.7. + */ +function BestFitSupportedLocales(availableLocales, requestedLocales) { + // don't have anything better + return LookupSupportedLocales(availableLocales, requestedLocales); +} + + +/** + * Returns the subset of requestedLocales for which availableLocales has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.8. + */ +function SupportedLocales(availableLocales, requestedLocales, options) { + /*jshint laxbreak: true */ + + // Step 1. + var matcher; + if (options !== undefined) { + // Steps 1.a-b. + options = ToObject(options); + matcher = options.localeMatcher; + + // Step 1.c. + if (matcher !== undefined) { + matcher = ToString(matcher); + if (matcher !== "lookup" && matcher !== "best fit") + ThrowRangeError(JSMSG_INVALID_LOCALE_MATCHER, matcher); + } + } + + // Steps 2-3. + var subset = (matcher === undefined || matcher === "best fit") + ? BestFitSupportedLocales(availableLocales, requestedLocales) + : LookupSupportedLocales(availableLocales, requestedLocales); + + // Step 4. + for (var i = 0; i < subset.length; i++) { + _DefineDataProperty(subset, i, subset[i], + ATTR_ENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE); + } + _DefineDataProperty(subset, "length", subset.length, + ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE); + + // Step 5. + return subset; +} + + +/** + * Extracts a property value from the provided options object, converts it to + * the required type, checks whether it is one of a list of allowed values, + * and fills in a fallback value if necessary. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.9. + */ +function GetOption(options, property, type, values, fallback) { + // Step 1. + var value = options[property]; + + // Step 2. + if (value !== undefined) { + // Steps 2.a-c. + if (type === "boolean") + value = ToBoolean(value); + else if (type === "string") + value = ToString(value); + else + assert(false, "GetOption"); + + // Step 2.d. + if (values !== undefined && callFunction(ArrayIndexOf, values, value) === -1) + ThrowRangeError(JSMSG_INVALID_OPTION_VALUE, property, value); + + // Step 2.e. + return value; + } + + // Step 3. + return fallback; +} + +/** + * Extracts a property value from the provided options object, converts it to a + * Number value, checks whether it is in the allowed range, and fills in a + * fallback value if necessary. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.10. + */ +function GetNumberOption(options, property, minimum, maximum, fallback) { + assert(typeof minimum === "number", "GetNumberOption"); + assert(typeof maximum === "number", "GetNumberOption"); + assert(fallback === undefined || (fallback >= minimum && fallback <= maximum), "GetNumberOption"); + + // Step 1. + var value = options[property]; + + // Step 2. + if (value !== undefined) { + value = ToNumber(value); + if (Number_isNaN(value) || value < minimum || value > maximum) + ThrowRangeError(JSMSG_INVALID_DIGITS_VALUE, value); + return std_Math_floor(value); + } + + // Step 3. + return fallback; +} + + +/********** Property access for Intl objects **********/ + + +/** + * Weak map used to track the initialize-as-Intl status (and, if an object has + * been so initialized, the Intl-specific internal properties) of all objects. + * Presence of an object as a key within this map indicates that the object has + * its [[initializedIntlObject]] internal property set to true. The associated + * value is an object whose structure is documented in |initializeIntlObject| + * below. + * + * Ideally we'd be using private symbols for internal properties, but + * SpiderMonkey doesn't have those yet. + */ +var internalsMap = new WeakMap(); + + +/** + * Set the [[initializedIntlObject]] internal property of |obj| to true. + */ +function initializeIntlObject(obj) { + assert(IsObject(obj), "Non-object passed to initializeIntlObject"); + + // Intl-initialized objects are weird. They have [[initializedIntlObject]] + // set on them, but they don't *necessarily* have any other properties. + + var internals = std_Object_create(null); + + // The meaning of an internals object for an object |obj| is as follows. + // + // If the .type is "partial", |obj| has [[initializedIntlObject]] set but + // nothing else. No other property of |internals| can be used. (This + // occurs when InitializeCollator or similar marks an object as + // [[initializedIntlObject]] but fails before marking it as the appropriate + // more-specific type ["Collator", "DateTimeFormat", "NumberFormat"].) + // + // Otherwise, the .type indicates the type of Intl object that |obj| is: + // "Collator", "DateTimeFormat", or "NumberFormat" (likely with more coming + // in future Intl specs). In these cases |obj| *conceptually* also has + // [[initializedCollator]] or similar set, and all the other properties + // implied by that. + // + // If |internals| doesn't have a "partial" .type, two additional properties + // have meaning. The .lazyData property stores information needed to + // compute -- without observable side effects -- the actual internal Intl + // properties of |obj|. If it is non-null, then the actual internal + // properties haven't been computed, and .lazyData must be processed by + // |setInternalProperties| before internal Intl property values are + // available. If it is null, then the .internalProps property contains an + // object whose properties are the internal Intl properties of |obj|. + + internals.type = "partial"; + internals.lazyData = null; + internals.internalProps = null; + + callFunction(std_WeakMap_set, internalsMap, obj, internals); + return internals; +} + + +/** + * Mark |internals| as having the given type and lazy data. + */ +function setLazyData(internals, type, lazyData) +{ + assert(internals.type === "partial", "can't set lazy data for anything but a newborn"); + assert(type === "Collator" || type === "DateTimeFormat" || type == "NumberFormat", "bad type"); + assert(IsObject(lazyData), "non-object lazy data"); + + // Set in reverse order so that the .type change is a barrier. + internals.lazyData = lazyData; + internals.type = type; +} + + +/** + * Set the internal properties object for an |internals| object previously + * associated with lazy data. + */ +function setInternalProperties(internals, internalProps) +{ + assert(internals.type !== "partial", "newborn internals can't have computed internals"); + assert(IsObject(internals.lazyData), "lazy data must exist already"); + assert(IsObject(internalProps), "internalProps argument should be an object"); + + // Set in reverse order so that the .lazyData nulling is a barrier. + internals.internalProps = internalProps; + internals.lazyData = null; +} + + +/** + * Get the existing internal properties out of a non-newborn |internals|, or + * null if none have been computed. + */ +function maybeInternalProperties(internals) +{ + assert(IsObject(internals), "non-object passed to maybeInternalProperties"); + assert(internals.type !== "partial", "maybeInternalProperties must only be used on completely-initialized internals objects"); + var lazyData = internals.lazyData; + if (lazyData) + return null; + assert(IsObject(internals.internalProps), "missing lazy data and computed internals"); + return internals.internalProps; +} + + +/** + * Return whether |obj| has an[[initializedIntlObject]] property set to true. + */ +function isInitializedIntlObject(obj) { +#ifdef DEBUG + var internals = callFunction(std_WeakMap_get, internalsMap, obj); + if (IsObject(internals)) { + assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type"); + var type = internals.type; + assert(type === "partial" || type === "Collator" || type === "DateTimeFormat" || type === "NumberFormat", "unexpected type"); + assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData"); + assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps"); + } else { + assert(internals === undefined, "bad mapping for |obj|"); + } +#endif + return callFunction(std_WeakMap_has, internalsMap, obj); +} + + +/** + * Check that |obj| meets the requirements for "this Collator object", "this + * NumberFormat object", or "this DateTimeFormat object" as used in the method + * with the given name. Throw a TypeError if |obj| doesn't meet these + * requirements. But if it does, return |obj|'s internals object (*not* the + * object holding its internal properties!), associated with it by + * |internalsMap|, with structure specified above. + * + * Spec: ECMAScript Internationalization API Specification, 10.3. + * Spec: ECMAScript Internationalization API Specification, 11.3. + * Spec: ECMAScript Internationalization API Specification, 12.3. + */ +function getIntlObjectInternals(obj, className, methodName) { + assert(typeof className === "string", "bad className for getIntlObjectInternals"); + + var internals = callFunction(std_WeakMap_get, internalsMap, obj); + assert(internals === undefined || isInitializedIntlObject(obj), "bad mapping in internalsMap"); + + if (internals === undefined || internals.type !== className) + ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, className, methodName, className); + + return internals; +} + + +/** + * Get the internal properties of known-Intl object |obj|. For use only by + * C++ code that knows what it's doing! + */ +function getInternals(obj) +{ + assert(isInitializedIntlObject(obj), "for use only on guaranteed Intl objects"); + + var internals = callFunction(std_WeakMap_get, internalsMap, obj); + + assert(internals.type !== "partial", "must have been successfully initialized"); + var lazyData = internals.lazyData; + if (!lazyData) + return internals.internalProps; + + var internalProps; + var type = internals.type; + if (type === "Collator") + internalProps = resolveCollatorInternals(lazyData) + else if (type === "DateTimeFormat") + internalProps = resolveDateTimeFormatInternals(lazyData) + else + internalProps = resolveNumberFormatInternals(lazyData); + setInternalProperties(internals, internalProps); + return internalProps; +} + + +/********** Intl.Collator **********/ + + +/** + * Mapping from Unicode extension keys for collation to options properties, + * their types and permissible values. + * + * Spec: ECMAScript Internationalization API Specification, 10.1.1. + */ +var collatorKeyMappings = { + kn: {property: "numeric", type: "boolean"}, + kf: {property: "caseFirst", type: "string", values: ["upper", "lower", "false"]} +}; + + +/** + * Compute an internal properties object from |lazyCollatorData|. + */ +function resolveCollatorInternals(lazyCollatorData) +{ + assert(IsObject(lazyCollatorData), "lazy data not an object?"); + + var internalProps = std_Object_create(null); + + // Step 7. + internalProps.usage = lazyCollatorData.usage; + + // Step 8. + var Collator = collatorInternalProperties; + + // Step 9. + var collatorIsSorting = lazyCollatorData.usage === "sort"; + var localeData = collatorIsSorting + ? Collator.sortLocaleData + : Collator.searchLocaleData; + + // Compute effective locale. + // Step 14. + var relevantExtensionKeys = Collator.relevantExtensionKeys; + + // Step 15. + var r = ResolveLocale(callFunction(Collator.availableLocales, Collator), + lazyCollatorData.requestedLocales, + lazyCollatorData.opt, + relevantExtensionKeys, + localeData); + + // Step 16. + internalProps.locale = r.locale; + + // Steps 17-19. + var key, property, value, mapping; + var i = 0, len = relevantExtensionKeys.length; + while (i < len) { + // Step 19.a. + key = relevantExtensionKeys[i]; + if (key === "co") { + // Step 19.b. + property = "collation"; + value = r.co === null ? "default" : r.co; + } else { + // Step 19.c. + mapping = collatorKeyMappings[key]; + property = mapping.property; + value = r[key]; + if (mapping.type === "boolean") + value = value === "true"; + } + + // Step 19.d. + internalProps[property] = value; + + // Step 19.e. + i++; + } + + // Compute remaining collation options. + // Steps 21-22. + var s = lazyCollatorData.rawSensitivity; + if (s === undefined) { + if (collatorIsSorting) { + // Step 21.a. + s = "variant"; + } else { + // Step 21.b. + var dataLocale = r.dataLocale; + var dataLocaleData = localeData(dataLocale); + s = dataLocaleData.sensitivity; + } + } + internalProps.sensitivity = s; + + // Step 24. + internalProps.ignorePunctuation = lazyCollatorData.ignorePunctuation; + + // Step 25. + internalProps.boundFormat = undefined; + + // The caller is responsible for associating |internalProps| with the right + // object using |setInternalProperties|. + return internalProps; +} + + +/** + * Returns an object containing the Collator internal properties of |obj|, or + * throws a TypeError if |obj| isn't Collator-initialized. + */ +function getCollatorInternals(obj, methodName) { + var internals = getIntlObjectInternals(obj, "Collator", methodName); + assert(internals.type === "Collator", "bad type escaped getIntlObjectInternals"); + + // If internal properties have already been computed, use them. + var internalProps = maybeInternalProperties(internals); + if (internalProps) + return internalProps; + + // Otherwise it's time to fully create them. + internalProps = resolveCollatorInternals(internals.lazyData); + setInternalProperties(internals, internalProps); + return internalProps; +} + + +/** + * Initializes an object as a Collator. + * + * This method is complicated a moderate bit by its implementing initialization + * as a *lazy* concept. Everything that must happen now, does -- but we defer + * all the work we can until the object is actually used as a Collator. This + * later work occurs in |resolveCollatorInternals|; steps not noted here occur + * there. + * + * Spec: ECMAScript Internationalization API Specification, 10.1.1. + */ +function InitializeCollator(collator, locales, options) { + assert(IsObject(collator), "InitializeCollator"); + + // Step 1. + if (isInitializedIntlObject(collator)) + ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); + + // Step 2. + var internals = initializeIntlObject(collator); + + // Lazy Collator data has the following structure: + // + // { + // requestedLocales: List of locales, + // usage: "sort" / "search", + // opt: // opt object computed in InitializeCollator + // { + // localeMatcher: "lookup" / "best fit", + // kn: true / false / undefined, + // kf: "upper" / "lower" / "false" / undefined + // } + // rawSensitivity: "base" / "accent" / "case" / "variant" / undefined, + // ignorePunctuation: true / false + // } + // + // Note that lazy data is only installed as a final step of initialization, + // so every Collator lazy data object has *all* these properties, never a + // subset of them. + var lazyCollatorData = std_Object_create(null); + + // Step 3. + var requestedLocales = CanonicalizeLocaleList(locales); + lazyCollatorData.requestedLocales = requestedLocales; + + // Steps 4-5. + // + // If we ever need more speed here at startup, we should try to detect the + // case where |options === undefined| and Object.prototype hasn't been + // mucked with. (|options| is fully consumed in this method, so it's not a + // concern that Object.prototype might be touched between now and when + // |resolveCollatorInternals| is called.) For now, just keep it simple. + if (options === undefined) + options = {}; + else + options = ToObject(options); + + // Compute options that impact interpretation of locale. + // Step 6. + var u = GetOption(options, "usage", "string", ["sort", "search"], "sort"); + lazyCollatorData.usage = u; + + // Step 10. + var opt = new Record(); + lazyCollatorData.opt = opt; + + // Steps 11-12. + var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit"); + opt.localeMatcher = matcher; + + // Step 13, unrolled. + var numericValue = GetOption(options, "numeric", "boolean", undefined, undefined); + if (numericValue !== undefined) + numericValue = numericValue ? 'true' : 'false'; + opt.kn = numericValue; + + var caseFirstValue = GetOption(options, "caseFirst", "string", ["upper", "lower", "false"], undefined); + opt.kf = caseFirstValue; + + // Compute remaining collation options. + // Step 20. + var s = GetOption(options, "sensitivity", "string", + ["base", "accent", "case", "variant"], undefined); + lazyCollatorData.rawSensitivity = s; + + // Step 23. + var ip = GetOption(options, "ignorePunctuation", "boolean", undefined, false); + lazyCollatorData.ignorePunctuation = ip; + + // Step 26. + // + // We've done everything that must be done now: mark the lazy data as fully + // computed and install it. + setLazyData(internals, "Collator", lazyCollatorData); +} + + +/** + * Returns the subset of the given locale list for which this locale list has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 10.2.2. + */ +function Intl_Collator_supportedLocalesOf(locales /*, options*/) { + var options = arguments.length > 1 ? arguments[1] : undefined; + + var availableLocales = callFunction(collatorInternalProperties.availableLocales, + collatorInternalProperties); + var requestedLocales = CanonicalizeLocaleList(locales); + return SupportedLocales(availableLocales, requestedLocales, options); +} + + +/** + * Collator internal properties. + * + * Spec: ECMAScript Internationalization API Specification, 9.1 and 10.2.3. + */ +var collatorInternalProperties = { + sortLocaleData: collatorSortLocaleData, + searchLocaleData: collatorSearchLocaleData, + _availableLocales: null, + availableLocales: function() + { + var locales = this._availableLocales; + if (locales) + return locales; + + locales = intl_Collator_availableLocales(); + addSpecialMissingLanguageTags(locales); + return (this._availableLocales = locales); + }, + relevantExtensionKeys: ["co", "kn"] +}; + + +function collatorSortLocaleData(locale) { + var collations = intl_availableCollations(locale); + callFunction(std_Array_unshift, collations, null); + return { + co: collations, + kn: ["false", "true"] + }; +} + + +function collatorSearchLocaleData(locale) { + return { + co: [null], + kn: ["false", "true"], + // In theory the default sensitivity is locale dependent; + // in reality the CLDR/ICU default strength is always tertiary. + sensitivity: "variant" + }; +} + + +/** + * Function to be bound and returned by Intl.Collator.prototype.format. + * + * Spec: ECMAScript Internationalization API Specification, 12.3.2. + */ +function collatorCompareToBind(x, y) { + // Steps 1.a.i-ii implemented by ECMAScript declaration binding instantiation, + // ES5.1 10.5, step 4.d.ii. + + // Step 1.a.iii-v. + var X = ToString(x); + var Y = ToString(y); + return intl_CompareStrings(this, X, Y); +} + + +/** + * Returns a function bound to this Collator that compares x (converted to a + * String value) and y (converted to a String value), + * and returns a number less than 0 if x < y, 0 if x = y, or a number greater + * than 0 if x > y according to the sort order for the locale and collation + * options of this Collator object. + * + * Spec: ECMAScript Internationalization API Specification, 10.3.2. + */ +function Intl_Collator_compare_get() { + // Check "this Collator object" per introduction of section 10.3. + var internals = getCollatorInternals(this, "compare"); + + // Step 1. + if (internals.boundCompare === undefined) { + // Step 1.a. + var F = collatorCompareToBind; + + // Step 1.b-d. + var bc = callFunction(FunctionBind, F, this); + internals.boundCompare = bc; + } + + // Step 2. + return internals.boundCompare; +} + + +/** + * Returns the resolved options for a Collator object. + * + * Spec: ECMAScript Internationalization API Specification, 10.3.3 and 10.4. + */ +function Intl_Collator_resolvedOptions() { + // Check "this Collator object" per introduction of section 10.3. + var internals = getCollatorInternals(this, "resolvedOptions"); + + var result = { + locale: internals.locale, + usage: internals.usage, + sensitivity: internals.sensitivity, + ignorePunctuation: internals.ignorePunctuation + }; + + var relevantExtensionKeys = collatorInternalProperties.relevantExtensionKeys; + for (var i = 0; i < relevantExtensionKeys.length; i++) { + var key = relevantExtensionKeys[i]; + var property = (key === "co") ? "collation" : collatorKeyMappings[key].property; + _DefineDataProperty(result, property, internals[property]); + } + return result; +} + + +/********** Intl.NumberFormat **********/ + + +/** + * NumberFormat internal properties. + * + * Spec: ECMAScript Internationalization API Specification, 9.1 and 11.2.3. + */ +var numberFormatInternalProperties = { + localeData: numberFormatLocaleData, + _availableLocales: null, + availableLocales: function() + { + var locales = this._availableLocales; + if (locales) + return locales; + + locales = intl_NumberFormat_availableLocales(); + addSpecialMissingLanguageTags(locales); + return (this._availableLocales = locales); + }, + relevantExtensionKeys: ["nu"] +}; + + +/** + * Compute an internal properties object from |lazyNumberFormatData|. + */ +function resolveNumberFormatInternals(lazyNumberFormatData) { + assert(IsObject(lazyNumberFormatData), "lazy data not an object?"); + + var internalProps = std_Object_create(null); + + // Step 3. + var requestedLocales = lazyNumberFormatData.requestedLocales; + + // Compute options that impact interpretation of locale. + // Step 6. + var opt = lazyNumberFormatData.opt; + + // Compute effective locale. + // Step 9. + var NumberFormat = numberFormatInternalProperties; + + // Step 10. + var localeData = NumberFormat.localeData; + + // Step 11. + var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat), + lazyNumberFormatData.requestedLocales, + lazyNumberFormatData.opt, + NumberFormat.relevantExtensionKeys, + localeData); + + // Steps 12-13. (Step 14 is not relevant to our implementation.) + internalProps.locale = r.locale; + internalProps.numberingSystem = r.nu; + + // Compute formatting options. + // Step 16. + var s = lazyNumberFormatData.style; + internalProps.style = s; + + // Steps 20, 22. + if (s === "currency") { + internalProps.currency = lazyNumberFormatData.currency; + internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay; + } + + // Step 24. + internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits; + + // Steps 27. + internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits; + + // Step 30. + internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits; + + // Step 33. + if ("minimumSignificantDigits" in lazyNumberFormatData) { + // Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the + // actual presence (versus undefined-ness) of these properties. + assert("maximumSignificantDigits" in lazyNumberFormatData, "min/max sig digits mismatch"); + internalProps.minimumSignificantDigits = lazyNumberFormatData.minimumSignificantDigits; + internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits; + } + + // Step 35. + internalProps.useGrouping = lazyNumberFormatData.useGrouping; + + // Step 42. + internalProps.boundFormat = undefined; + + // The caller is responsible for associating |internalProps| with the right + // object using |setInternalProperties|. + return internalProps; +} + + +/** + * Returns an object containing the NumberFormat internal properties of |obj|, + * or throws a TypeError if |obj| isn't NumberFormat-initialized. + */ +function getNumberFormatInternals(obj, methodName) { + var internals = getIntlObjectInternals(obj, "NumberFormat", methodName); + assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals"); + + // If internal properties have already been computed, use them. + var internalProps = maybeInternalProperties(internals); + if (internalProps) + return internalProps; + + // Otherwise it's time to fully create them. + internalProps = resolveNumberFormatInternals(internals.lazyData); + setInternalProperties(internals, internalProps); + return internalProps; +} + + +/** + * Initializes an object as a NumberFormat. + * + * This method is complicated a moderate bit by its implementing initialization + * as a *lazy* concept. Everything that must happen now, does -- but we defer + * all the work we can until the object is actually used as a NumberFormat. + * This later work occurs in |resolveNumberFormatInternals|; steps not noted + * here occur there. + * + * Spec: ECMAScript Internationalization API Specification, 11.1.1. + */ +function InitializeNumberFormat(numberFormat, locales, options) { + assert(IsObject(numberFormat), "InitializeNumberFormat"); + + // Step 1. + if (isInitializedIntlObject(numberFormat)) + ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); + + // Step 2. + var internals = initializeIntlObject(numberFormat); + + // Lazy NumberFormat data has the following structure: + // + // { + // requestedLocales: List of locales, + // style: "decimal" / "percent" / "currency", + // + // // fields present only if style === "currency": + // currency: a well-formed currency code (IsWellFormedCurrencyCode), + // currencyDisplay: "code" / "symbol" / "name", + // + // opt: // opt object computed in InitializeNumberFormat + // { + // localeMatcher: "lookup" / "best fit", + // } + // + // minimumIntegerDigits: integer ∈ [1, 21], + // minimumFractionDigits: integer ∈ [0, 20], + // maximumFractionDigits: integer ∈ [0, 20], + // + // // optional + // minimumSignificantDigits: integer ∈ [1, 21], + // maximumSignificantDigits: integer ∈ [1, 21], + // + // useGrouping: true / false, + // } + // + // Note that lazy data is only installed as a final step of initialization, + // so every Collator lazy data object has *all* these properties, never a + // subset of them. + var lazyNumberFormatData = std_Object_create(null); + + // Step 3. + var requestedLocales = CanonicalizeLocaleList(locales); + lazyNumberFormatData.requestedLocales = requestedLocales; + + // Steps 4-5. + // + // If we ever need more speed here at startup, we should try to detect the + // case where |options === undefined| and Object.prototype hasn't been + // mucked with. (|options| is fully consumed in this method, so it's not a + // concern that Object.prototype might be touched between now and when + // |resolveNumberFormatInternals| is called.) For now just keep it simple. + if (options === undefined) + options = {}; + else + options = ToObject(options); + + // Compute options that impact interpretation of locale. + // Step 6. + var opt = new Record(); + lazyNumberFormatData.opt = opt; + + // Steps 7-8. + var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit"); + opt.localeMatcher = matcher; + + // Compute formatting options. + // Step 15. + var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal"); + lazyNumberFormatData.style = s; + + // Steps 17-20. + var c = GetOption(options, "currency", "string", undefined, undefined); + if (c !== undefined && !IsWellFormedCurrencyCode(c)) + ThrowRangeError(JSMSG_INVALID_CURRENCY_CODE, c); + var cDigits; + if (s === "currency") { + if (c === undefined) + ThrowTypeError(JSMSG_UNDEFINED_CURRENCY); + + // Steps 20.a-c. + c = toASCIIUpperCase(c); + lazyNumberFormatData.currency = c; + cDigits = CurrencyDigits(c); + } + + // Step 21. + var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol"); + if (s === "currency") + lazyNumberFormatData.currencyDisplay = cd; + + // Step 23. + var mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1); + lazyNumberFormatData.minimumIntegerDigits = mnid; + + // Steps 25-26. + var mnfdDefault = (s === "currency") ? cDigits : 0; + var mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault); + lazyNumberFormatData.minimumFractionDigits = mnfd; + + // Steps 28-29. + var mxfdDefault; + if (s === "currency") + mxfdDefault = std_Math_max(mnfd, cDigits); + else if (s === "percent") + mxfdDefault = std_Math_max(mnfd, 0); + else + mxfdDefault = std_Math_max(mnfd, 3); + var mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20, mxfdDefault); + lazyNumberFormatData.maximumFractionDigits = mxfd; + + // Steps 31-32. + var mnsd = options.minimumSignificantDigits; + var mxsd = options.maximumSignificantDigits; + + // Step 33. + if (mnsd !== undefined || mxsd !== undefined) { + mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1); + mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21); + lazyNumberFormatData.minimumSignificantDigits = mnsd; + lazyNumberFormatData.maximumSignificantDigits = mxsd; + } + + // Step 34. + var g = GetOption(options, "useGrouping", "boolean", undefined, true); + lazyNumberFormatData.useGrouping = g; + + // Step 43. + // + // We've done everything that must be done now: mark the lazy data as fully + // computed and install it. + setLazyData(internals, "NumberFormat", lazyNumberFormatData); +} + + +/** + * Mapping from currency codes to the number of decimal digits used for them. + * Default is 2 digits. + * + * Spec: ISO 4217 Currency and Funds Code List. + * http://www.currency-iso.org/en/home/tables/table-a1.html + */ +var currencyDigits = { + BHD: 3, + BIF: 0, + BYR: 0, + CLF: 4, + CLP: 0, + DJF: 0, + GNF: 0, + IQD: 3, + ISK: 0, + JOD: 3, + JPY: 0, + KMF: 0, + KRW: 0, + KWD: 3, + LYD: 3, + OMR: 3, + PYG: 0, + RWF: 0, + TND: 3, + UGX: 0, + UYI: 0, + VND: 0, + VUV: 0, + XAF: 0, + XOF: 0, + XPF: 0 +}; + + +/** + * Returns the number of decimal digits to be used for the given currency. + * + * Spec: ECMAScript Internationalization API Specification, 11.1.1. + */ +function getCurrencyDigitsRE() { + return internalIntlRegExps.currencyDigitsRE || + (internalIntlRegExps.currencyDigitsRE = RegExpCreate("^[A-Z]{3}$")); +} +function CurrencyDigits(currency) { + assert(typeof currency === "string", "CurrencyDigits"); + assert(regexp_test_no_statics(getCurrencyDigitsRE(), currency), "CurrencyDigits"); + + if (callFunction(std_Object_hasOwnProperty, currencyDigits, currency)) + return currencyDigits[currency]; + return 2; +} + + +/** + * Returns the subset of the given locale list for which this locale list has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 11.2.2. + */ +function Intl_NumberFormat_supportedLocalesOf(locales /*, options*/) { + var options = arguments.length > 1 ? arguments[1] : undefined; + + var availableLocales = callFunction(numberFormatInternalProperties.availableLocales, + numberFormatInternalProperties); + var requestedLocales = CanonicalizeLocaleList(locales); + return SupportedLocales(availableLocales, requestedLocales, options); +} + + +function getNumberingSystems(locale) { + // ICU doesn't have an API to determine the set of numbering systems + // supported for a locale; it generally pretends that any numbering system + // can be used with any locale. Supporting a decimal numbering system + // (where only the digits are replaced) is easy, so we offer them all here. + // Algorithmic numbering systems are typically tied to one locale, so for + // lack of information we don't offer them. To increase chances that + // other software will process output correctly, we further restrict to + // those decimal numbering systems explicitly listed in table 2 of + // the ECMAScript Internationalization API Specification, 11.3.2, which + // in turn are those with full specifications in version 21 of Unicode + // Technical Standard #35 using digits that were defined in Unicode 5.0, + // the Unicode version supported in Windows Vista. + // The one thing we can find out from ICU is the default numbering system + // for a locale. + var defaultNumberingSystem = intl_numberingSystem(locale); + return [ + defaultNumberingSystem, + "arab", "arabext", "bali", "beng", "deva", + "fullwide", "gujr", "guru", "hanidec", "khmr", + "knda", "laoo", "latn", "limb", "mlym", + "mong", "mymr", "orya", "tamldec", "telu", + "thai", "tibt" + ]; +} + + +function numberFormatLocaleData(locale) { + return { + nu: getNumberingSystems(locale) + }; +} + + +/** + * Function to be bound and returned by Intl.NumberFormat.prototype.format. + * + * Spec: ECMAScript Internationalization API Specification, 11.3.2. + */ +function numberFormatFormatToBind(value) { + // Steps 1.a.i implemented by ECMAScript declaration binding instantiation, + // ES5.1 10.5, step 4.d.ii. + + // Step 1.a.ii-iii. + var x = ToNumber(value); + return intl_FormatNumber(this, x); +} + + +/** + * Returns a function bound to this NumberFormat that returns a String value + * representing the result of calling ToNumber(value) according to the + * effective locale and the formatting options of this NumberFormat. + * + * Spec: ECMAScript Internationalization API Specification, 11.3.2. + */ +function Intl_NumberFormat_format_get() { + // Check "this NumberFormat object" per introduction of section 11.3. + var internals = getNumberFormatInternals(this, "format"); + + // Step 1. + if (internals.boundFormat === undefined) { + // Step 1.a. + var F = numberFormatFormatToBind; + + // Step 1.b-d. + var bf = callFunction(FunctionBind, F, this); + internals.boundFormat = bf; + } + // Step 2. + return internals.boundFormat; +} + + +/** + * Returns the resolved options for a NumberFormat object. + * + * Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4. + */ +function Intl_NumberFormat_resolvedOptions() { + // Check "this NumberFormat object" per introduction of section 11.3. + var internals = getNumberFormatInternals(this, "resolvedOptions"); + + var result = { + locale: internals.locale, + numberingSystem: internals.numberingSystem, + style: internals.style, + minimumIntegerDigits: internals.minimumIntegerDigits, + minimumFractionDigits: internals.minimumFractionDigits, + maximumFractionDigits: internals.maximumFractionDigits, + useGrouping: internals.useGrouping + }; + var optionalProperties = [ + "currency", + "currencyDisplay", + "minimumSignificantDigits", + "maximumSignificantDigits" + ]; + for (var i = 0; i < optionalProperties.length; i++) { + var p = optionalProperties[i]; + if (callFunction(std_Object_hasOwnProperty, internals, p)) + _DefineDataProperty(result, p, internals[p]); + } + return result; +} + + +/********** Intl.DateTimeFormat **********/ + + +/** + * Compute an internal properties object from |lazyDateTimeFormatData|. + */ +function resolveDateTimeFormatInternals(lazyDateTimeFormatData) { + assert(IsObject(lazyDateTimeFormatData), "lazy data not an object?"); + + // Lazy DateTimeFormat data has the following structure: + // + // { + // requestedLocales: List of locales, + // + // localeOpt: // *first* opt computed in InitializeDateTimeFormat + // { + // localeMatcher: "lookup" / "best fit", + // + // hour12: true / false, // optional + // } + // + // timeZone: IANA time zone name, + // + // formatOpt: // *second* opt computed in InitializeDateTimeFormat + // { + // // all the properties/values listed in Table 3 + // // (weekday, era, year, month, day, &c.) + // } + // + // formatMatcher: "basic" / "best fit", + // } + // + // Note that lazy data is only installed as a final step of initialization, + // so every DateTimeFormat lazy data object has *all* these properties, + // never a subset of them. + + var internalProps = std_Object_create(null); + + // Compute effective locale. + // Step 8. + var DateTimeFormat = dateTimeFormatInternalProperties; + + // Step 9. + var localeData = DateTimeFormat.localeData; + + // Step 10. + var r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat), + lazyDateTimeFormatData.requestedLocales, + lazyDateTimeFormatData.localeOpt, + DateTimeFormat.relevantExtensionKeys, + localeData); + + // Steps 11-13. + internalProps.locale = r.locale; + internalProps.calendar = r.ca; + internalProps.numberingSystem = r.nu; + + // Compute formatting options. + // Step 14. + var dataLocale = r.dataLocale; + + // Steps 15-17. + var tz = lazyDateTimeFormatData.timeZone; + if (tz === undefined) { + // Step 16. + tz = DefaultTimeZone(); + } + internalProps.timeZone = tz; + + // Step 18. + var formatOpt = lazyDateTimeFormatData.formatOpt; + + // Steps 27-28, more or less - see comment after this function. + var pattern = toBestICUPattern(dataLocale, formatOpt); + + // Step 29. + internalProps.pattern = pattern; + + // Step 30. + internalProps.boundFormat = undefined; + + // The caller is responsible for associating |internalProps| with the right + // object using |setInternalProperties|. + return internalProps; +} + + +/** + * Returns an object containing the DateTimeFormat internal properties of |obj|, + * or throws a TypeError if |obj| isn't DateTimeFormat-initialized. + */ +function getDateTimeFormatInternals(obj, methodName) { + var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName); + assert(internals.type === "DateTimeFormat", "bad type escaped getIntlObjectInternals"); + + // If internal properties have already been computed, use them. + var internalProps = maybeInternalProperties(internals); + if (internalProps) + return internalProps; + + // Otherwise it's time to fully create them. + internalProps = resolveDateTimeFormatInternals(internals.lazyData); + setInternalProperties(internals, internalProps); + return internalProps; +} + +/** + * Components of date and time formats and their values. + * + * Spec: ECMAScript Internationalization API Specification, 12.1.1. + */ +var dateTimeComponentValues = { + weekday: ["narrow", "short", "long"], + era: ["narrow", "short", "long"], + year: ["2-digit", "numeric"], + month: ["2-digit", "numeric", "narrow", "short", "long"], + day: ["2-digit", "numeric"], + hour: ["2-digit", "numeric"], + minute: ["2-digit", "numeric"], + second: ["2-digit", "numeric"], + timeZoneName: ["short", "long"] +}; + + +var dateTimeComponents = std_Object_getOwnPropertyNames(dateTimeComponentValues); + + +/** + * Initializes an object as a DateTimeFormat. + * + * This method is complicated a moderate bit by its implementing initialization + * as a *lazy* concept. Everything that must happen now, does -- but we defer + * all the work we can until the object is actually used as a DateTimeFormat. + * This later work occurs in |resolveDateTimeFormatInternals|; steps not noted + * here occur there. + * + * Spec: ECMAScript Internationalization API Specification, 12.1.1. + */ +function InitializeDateTimeFormat(dateTimeFormat, locales, options) { + assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat"); + + // Step 1. + if (isInitializedIntlObject(dateTimeFormat)) + ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); + + // Step 2. + var internals = initializeIntlObject(dateTimeFormat); + + // Lazy DateTimeFormat data has the following structure: + // + // { + // requestedLocales: List of locales, + // + // localeOpt: // *first* opt computed in InitializeDateTimeFormat + // { + // localeMatcher: "lookup" / "best fit", + // } + // + // timeZone: IANA time zone name, + // + // formatOpt: // *second* opt computed in InitializeDateTimeFormat + // { + // // all the properties/values listed in Table 3 + // // (weekday, era, year, month, day, &c.) + // + // hour12: true / false // optional + // } + // + // formatMatcher: "basic" / "best fit", + // } + // + // Note that lazy data is only installed as a final step of initialization, + // so every DateTimeFormat lazy data object has *all* these properties, + // never a subset of them. + var lazyDateTimeFormatData = std_Object_create(null); + + // Step 3. + var requestedLocales = CanonicalizeLocaleList(locales); + lazyDateTimeFormatData.requestedLocales = requestedLocales; + + // Step 4. + options = ToDateTimeOptions(options, "any", "date"); + + // Compute options that impact interpretation of locale. + // Step 5. + var localeOpt = new Record(); + lazyDateTimeFormatData.localeOpt = localeOpt; + + // Steps 6-7. + var localeMatcher = + GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], + "best fit"); + localeOpt.localeMatcher = localeMatcher; + + // Steps 15-17. + var tz = options.timeZone; + if (tz !== undefined) { + // Step 15.a. + tz = ToString(tz); + + // Step 15.b. + var timeZone = intl_IsValidTimeZoneName(tz); + if (timeZone === null) + ThrowRangeError(JSMSG_INVALID_TIME_ZONE, tz); + + // Step 15.c. + tz = CanonicalizeTimeZoneName(timeZone); + } + lazyDateTimeFormatData.timeZone = tz; + + // Step 18. + var formatOpt = new Record(); + lazyDateTimeFormatData.formatOpt = formatOpt; + + // Step 19. + var i, prop; + for (i = 0; i < dateTimeComponents.length; i++) { + prop = dateTimeComponents[i]; + var value = GetOption(options, prop, "string", dateTimeComponentValues[prop], undefined); + formatOpt[prop] = value; + } + + // Steps 20-21 provided by ICU - see comment after this function. + + // Step 22. + // + // For some reason (ICU not exposing enough interface?) we drop the + // requested format matcher on the floor after this. In any case, even if + // doing so is justified, we have to do this work here in case it triggers + // getters or similar. (bug 852837) + var formatMatcher = + GetOption(options, "formatMatcher", "string", ["basic", "best fit"], + "best fit"); + + // Steps 23-25 provided by ICU, more or less - see comment after this function. + + // Step 26. + var hr12 = GetOption(options, "hour12", "boolean", undefined, undefined); + + // Pass hr12 on to ICU. + if (hr12 !== undefined) + formatOpt.hour12 = hr12; + + // Step 31. + // + // We've done everything that must be done now: mark the lazy data as fully + // computed and install it. + setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData); +} + + +// Intl.DateTimeFormat and ICU skeletons and patterns +// ================================================== +// +// Different locales have different ways to display dates using the same +// basic components. For example, en-US might use "Sept. 24, 2012" while +// fr-FR might use "24 Sept. 2012". The intent of Intl.DateTimeFormat is to +// permit production of a format for the locale that best matches the +// set of date-time components and their desired representation as specified +// by the API client. +// +// ICU supports specification of date and time formats in three ways: +// +// 1) A style is just one of the identifiers FULL, LONG, MEDIUM, or SHORT. +// The date-time components included in each style and their representation +// are defined by ICU using CLDR locale data (CLDR is the Unicode +// Consortium's Common Locale Data Repository). +// +// 2) A skeleton is a string specifying which date-time components to include, +// and which representations to use for them. For example, "yyyyMMMMdd" +// specifies a year with at least four digits, a full month name, and a +// two-digit day. It does not specify in which order the components appear, +// how they are separated, the localized strings for textual components +// (such as weekday or month), whether the month is in format or +// stand-alone form¹, or the numbering system used for numeric components. +// All that information is filled in by ICU using CLDR locale data. +// ¹ The format form is the one used in formatted strings that include a +// day; the stand-alone form is used when not including days, e.g., in +// calendar headers. The two forms differ at least in some Slavic languages, +// e.g. Russian: "22 марта 2013 г." vs. "Март 2013". +// +// 3) A pattern is a string specifying which date-time components to include, +// in which order, with which separators, in which grammatical case. For +// example, "EEEE, d MMMM y" specifies the full localized weekday name, +// followed by comma and space, followed by the day, followed by space, +// followed by the full month name in format form, followed by space, +// followed by the full year. It +// still does not specify localized strings for textual components and the +// numbering system - these are determined by ICU using CLDR locale data or +// possibly API parameters. +// +// All actual formatting in ICU is done with patterns; styles and skeletons +// have to be mapped to patterns before processing. +// +// The options of DateTimeFormat most closely correspond to ICU skeletons. This +// implementation therefore, in the toBestICUPattern function, converts +// DateTimeFormat options to ICU skeletons, and then lets ICU map skeletons to +// actual ICU patterns. The pattern may not directly correspond to what the +// skeleton requests, as the mapper (UDateTimePatternGenerator) is constrained +// by the available locale data for the locale. The resulting ICU pattern is +// kept as the DateTimeFormat's [[pattern]] internal property and passed to ICU +// in the format method. +// +// An ICU pattern represents the information of the following DateTimeFormat +// internal properties described in the specification, which therefore don't +// exist separately in the implementation: +// - [[weekday]], [[era]], [[year]], [[month]], [[day]], [[hour]], [[minute]], +// [[second]], [[timeZoneName]] +// - [[hour12]] +// - [[hourNo0]] +// When needed for the resolvedOptions method, the resolveICUPattern function +// maps the instance's ICU pattern back to the specified properties of the +// object returned by resolvedOptions. +// +// ICU date-time skeletons and patterns aren't fully documented in the ICU +// documentation (see http://bugs.icu-project.org/trac/ticket/9627). The best +// documentation at this point is in UTR 35: +// http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns + + +/** + * Returns an ICU pattern string for the given locale and representing the + * specified options as closely as possible given available locale data. + */ +function toBestICUPattern(locale, options) { + // Create an ICU skeleton representing the specified options. See + // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table + var skeleton = ""; + switch (options.weekday) { + case "narrow": + skeleton += "EEEEE"; + break; + case "short": + skeleton += "E"; + break; + case "long": + skeleton += "EEEE"; + } + switch (options.era) { + case "narrow": + skeleton += "GGGGG"; + break; + case "short": + skeleton += "G"; + break; + case "long": + skeleton += "GGGG"; + break; + } + switch (options.year) { + case "2-digit": + skeleton += "yy"; + break; + case "numeric": + skeleton += "y"; + break; + } + switch (options.month) { + case "2-digit": + skeleton += "MM"; + break; + case "numeric": + skeleton += "M"; + break; + case "narrow": + skeleton += "MMMMM"; + break; + case "short": + skeleton += "MMM"; + break; + case "long": + skeleton += "MMMM"; + break; + } + switch (options.day) { + case "2-digit": + skeleton += "dd"; + break; + case "numeric": + skeleton += "d"; + break; + } + var hourSkeletonChar = "j"; + if (options.hour12 !== undefined) { + if (options.hour12) + hourSkeletonChar = "h"; + else + hourSkeletonChar = "H"; + } + switch (options.hour) { + case "2-digit": + skeleton += hourSkeletonChar + hourSkeletonChar; + break; + case "numeric": + skeleton += hourSkeletonChar; + break; + } + switch (options.minute) { + case "2-digit": + skeleton += "mm"; + break; + case "numeric": + skeleton += "m"; + break; + } + switch (options.second) { + case "2-digit": + skeleton += "ss"; + break; + case "numeric": + skeleton += "s"; + break; + } + switch (options.timeZoneName) { + case "short": + skeleton += "z"; + break; + case "long": + skeleton += "zzzz"; + break; + } + + // Let ICU convert the ICU skeleton to an ICU pattern for the given locale. + return intl_patternForSkeleton(locale, skeleton); +} + + +/** + * Returns a new options object that includes the provided options (if any) + * and fills in default components if required components are not defined. + * Required can be "date", "time", or "any". + * Defaults can be "date", "time", or "all". + * + * Spec: ECMAScript Internationalization API Specification, 12.1.1. + */ +function ToDateTimeOptions(options, required, defaults) { + assert(typeof required === "string", "ToDateTimeOptions"); + assert(typeof defaults === "string", "ToDateTimeOptions"); + + // Steps 1-3. + if (options === undefined) + options = null; + else + options = ToObject(options); + options = std_Object_create(options); + + // Step 4. + var needDefaults = true; + + // Step 5. + if ((required === "date" || required === "any") && + (options.weekday !== undefined || options.year !== undefined || + options.month !== undefined || options.day !== undefined)) + { + needDefaults = false; + } + + // Step 6. + if ((required === "time" || required === "any") && + (options.hour !== undefined || options.minute !== undefined || + options.second !== undefined)) + { + needDefaults = false; + } + + // Step 7. + if (needDefaults && (defaults === "date" || defaults === "all")) { + // The specification says to call [[DefineOwnProperty]] with false for + // the Throw parameter, while Object.defineProperty uses true. For the + // calls here, the difference doesn't matter because we're adding + // properties to a new object. + _DefineDataProperty(options, "year", "numeric"); + _DefineDataProperty(options, "month", "numeric"); + _DefineDataProperty(options, "day", "numeric"); + } + + // Step 8. + if (needDefaults && (defaults === "time" || defaults === "all")) { + // See comment for step 7. + _DefineDataProperty(options, "hour", "numeric"); + _DefineDataProperty(options, "minute", "numeric"); + _DefineDataProperty(options, "second", "numeric"); + } + + // Step 9. + return options; +} + + +/** + * Compares the date and time components requested by options with the available + * date and time formats in formats, and selects the best match according + * to a specified basic matching algorithm. + * + * Spec: ECMAScript Internationalization API Specification, 12.1.1. + */ +function BasicFormatMatcher(options, formats) { + // Steps 1-6. + var removalPenalty = 120, + additionPenalty = 20, + longLessPenalty = 8, + longMorePenalty = 6, + shortLessPenalty = 6, + shortMorePenalty = 3; + + // Table 3. + var properties = ["weekday", "era", "year", "month", "day", + "hour", "minute", "second", "timeZoneName"]; + + // Step 11.c.vi.1. + var values = ["2-digit", "numeric", "narrow", "short", "long"]; + + // Steps 7-8. + var bestScore = -Infinity; + var bestFormat; + + // Steps 9-11. + var i = 0; + var len = formats.length; + while (i < len) { + // Steps 11.a-b. + var format = formats[i]; + var score = 0; + + // Step 11.c. + var formatProp; + for (var j = 0; j < properties.length; j++) { + var property = properties[j]; + + // Step 11.c.i. + var optionsProp = options[property]; + // Step missing from spec. + // https://bugs.ecmascript.org/show_bug.cgi?id=1254 + formatProp = undefined; + + // Steps 11.c.ii-iii. + if (callFunction(std_Object_hasOwnProperty, format, property)) + formatProp = format[property]; + + if (optionsProp === undefined && formatProp !== undefined) { + // Step 11.c.iv. + score -= additionPenalty; + } else if (optionsProp !== undefined && formatProp === undefined) { + // Step 11.c.v. + score -= removalPenalty; + } else { + // Step 11.c.vi. + var optionsPropIndex = callFunction(ArrayIndexOf, values, optionsProp); + var formatPropIndex = callFunction(ArrayIndexOf, values, formatProp); + var delta = std_Math_max(std_Math_min(formatPropIndex - optionsPropIndex, 2), -2); + if (delta === 2) + score -= longMorePenalty; + else if (delta === 1) + score -= shortMorePenalty; + else if (delta === -1) + score -= shortLessPenalty; + else if (delta === -2) + score -= longLessPenalty; + } + } + + // Step 11.d. + if (score > bestScore) { + bestScore = score; + bestFormat = format; + } + + // Step 11.e. + i++; + } + + // Step 12. + return bestFormat; +} + + +/** + * Compares the date and time components requested by options with the available + * date and time formats in formats, and selects the best match according + * to an unspecified best-fit matching algorithm. + * + * Spec: ECMAScript Internationalization API Specification, 12.1.1. + */ +function BestFitFormatMatcher(options, formats) { + // this implementation doesn't have anything better + return BasicFormatMatcher(options, formats); +} + + +/** + * Returns the subset of the given locale list for which this locale list has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 12.2.2. + */ +function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) { + var options = arguments.length > 1 ? arguments[1] : undefined; + + var availableLocales = callFunction(dateTimeFormatInternalProperties.availableLocales, + dateTimeFormatInternalProperties); + var requestedLocales = CanonicalizeLocaleList(locales); + return SupportedLocales(availableLocales, requestedLocales, options); +} + + +/** + * DateTimeFormat internal properties. + * + * Spec: ECMAScript Internationalization API Specification, 9.1 and 12.2.3. + */ +var dateTimeFormatInternalProperties = { + localeData: dateTimeFormatLocaleData, + _availableLocales: null, + availableLocales: function() + { + var locales = this._availableLocales; + if (locales) + return locales; + + locales = intl_DateTimeFormat_availableLocales(); + addSpecialMissingLanguageTags(locales); + return (this._availableLocales = locales); + }, + relevantExtensionKeys: ["ca", "nu"] +}; + + +function dateTimeFormatLocaleData(locale) { + return { + ca: intl_availableCalendars(locale), + nu: getNumberingSystems(locale) + }; +} + + +/** + * Function to be bound and returned by Intl.DateTimeFormat.prototype.format. + * + * Spec: ECMAScript Internationalization API Specification, 12.3.2. + */ +function dateTimeFormatFormatToBind() { + // Steps 1.a.i-ii + var date = arguments.length > 0 ? arguments[0] : undefined; + var x = (date === undefined) ? std_Date_now() : ToNumber(date); + + // Step 1.a.iii. + return intl_FormatDateTime(this, x, false); +} + +/** + * Returns a function bound to this DateTimeFormat that returns a String value + * representing the result of calling ToNumber(date) according to the + * effective locale and the formatting options of this DateTimeFormat. + * + * Spec: ECMAScript Internationalization API Specification, 12.3.2. + */ +function Intl_DateTimeFormat_format_get() { + // Check "this DateTimeFormat object" per introduction of section 12.3. + var internals = getDateTimeFormatInternals(this, "format"); + + // Step 1. + if (internals.boundFormat === undefined) { + // Step 1.a. + var F = dateTimeFormatFormatToBind; + + // Step 1.b-d. + var bf = callFunction(FunctionBind, F, this); + internals.boundFormat = bf; + } + + // Step 2. + return internals.boundFormat; +} + + +function Intl_DateTimeFormat_formatToParts() { + // Check "this DateTimeFormat object" per introduction of section 12.3. + getDateTimeFormatInternals(this, "formatToParts"); + + // Steps 1.a.i-ii + var date = arguments.length > 0 ? arguments[0] : undefined; + var x = (date === undefined) ? std_Date_now() : ToNumber(date); + + // Step 1.a.iii. + return intl_FormatDateTime(this, x, true); +} + + +/** + * Returns the resolved options for a DateTimeFormat object. + * + * Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4. + */ +function Intl_DateTimeFormat_resolvedOptions() { + // Check "this DateTimeFormat object" per introduction of section 12.3. + var internals = getDateTimeFormatInternals(this, "resolvedOptions"); + + var result = { + locale: internals.locale, + calendar: internals.calendar, + numberingSystem: internals.numberingSystem, + timeZone: internals.timeZone + }; + resolveICUPattern(internals.pattern, result); + return result; +} + + +// Table mapping ICU pattern characters back to the corresponding date-time +// components of DateTimeFormat. See +// http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table +var icuPatternCharToComponent = { + E: "weekday", + G: "era", + y: "year", + M: "month", + L: "month", + d: "day", + h: "hour", + H: "hour", + k: "hour", + K: "hour", + m: "minute", + s: "second", + z: "timeZoneName", + v: "timeZoneName", + V: "timeZoneName" +}; + + +/** + * Maps an ICU pattern string to a corresponding set of date-time components + * and their values, and adds properties for these components to the result + * object, which will be returned by the resolvedOptions method. For the + * interpretation of ICU pattern characters, see + * http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table + */ +function resolveICUPattern(pattern, result) { + assert(IsObject(result), "resolveICUPattern"); + var i = 0; + while (i < pattern.length) { + var c = pattern[i++]; + if (c === "'") { + while (i < pattern.length && pattern[i] !== "'") + i++; + i++; + } else { + var count = 1; + while (i < pattern.length && pattern[i] === c) { + i++; + count++; + } + var value; + switch (c) { + // "text" cases + case "G": + case "E": + case "z": + case "v": + case "V": + if (count <= 3) + value = "short"; + else if (count === 4) + value = "long"; + else + value = "narrow"; + break; + // "number" cases + case "y": + case "d": + case "h": + case "H": + case "m": + case "s": + case "k": + case "K": + if (count === 2) + value = "2-digit"; + else + value = "numeric"; + break; + // "text & number" cases + case "M": + case "L": + if (count === 1) + value = "numeric"; + else if (count === 2) + value = "2-digit"; + else if (count === 3) + value = "short"; + else if (count === 4) + value = "long"; + else + value = "narrow"; + break; + default: + // skip other pattern characters and literal text + } + if (callFunction(std_Object_hasOwnProperty, icuPatternCharToComponent, c)) + _DefineDataProperty(result, icuPatternCharToComponent[c], value); + if (c === "h" || c === "K") + _DefineDataProperty(result, "hour12", true); + else if (c === "H" || c === "k") + _DefineDataProperty(result, "hour12", false); + } + } +} + +function Intl_getCanonicalLocales(locales) { + let codes = CanonicalizeLocaleList(locales); + let result = []; + + let len = codes.length; + let k = 0; + + while (k < len) { + _DefineDataProperty(result, k, codes[k]); + k++; + } + return result; +} + +function Intl_getCalendarInfo(locales) { + const requestedLocales = CanonicalizeLocaleList(locales); + + const DateTimeFormat = dateTimeFormatInternalProperties; + const localeData = DateTimeFormat.localeData; + + const localeOpt = new Record(); + localeOpt.localeMatcher = "best fit"; + + const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat), + requestedLocales, + localeOpt, + DateTimeFormat.relevantExtensionKeys, + localeData); + + const result = intl_GetCalendarInfo(r.locale); + result.calendar = r.ca; + result.locale = r.locale; + + return result; +} |