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