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