| // Copyright (C) 2011 2012 Norbert Lindenberg. All rights reserved. |
| // Copyright (C) 2012 2013 Mozilla Corporation. All rights reserved. |
| // This code is governed by the BSD license found in the LICENSE file. |
| /*--- |
| description: | |
| This file contains shared functions for the tests in the conformance test |
| suite for the ECMAScript Internationalization API. |
| author: Norbert Lindenberg |
| defines: |
| - testWithIntlConstructors |
| - taintDataProperty |
| - taintMethod |
| - taintProperties |
| - taintArray |
| - getLocaleSupportInfo |
| - getInvalidLanguageTags |
| - isCanonicalizedStructurallyValidLanguageTag |
| - getInvalidLocaleArguments |
| - testOption |
| - testForUnwantedRegExpChanges |
| - isValidNumberingSystem |
| - testNumberFormat |
| - getDateTimeComponents |
| - getDateTimeComponentValues |
| - isCanonicalizedStructurallyValidTimeZoneName |
| ---*/ |
| /** |
| */ |
| |
| |
| /** |
| * @description Calls the provided function for every service constructor in |
| * the Intl object. |
| * @param {Function} f the function to call for each service constructor in |
| * the Intl object. |
| * @param {Function} Constructor the constructor object to test with. |
| */ |
| function testWithIntlConstructors(f) { |
| var constructors = ["Collator", "NumberFormat", "DateTimeFormat"]; |
| |
| // Optionally supported Intl constructors. |
| ["PluralRules"].forEach(function(constructor) { |
| if (typeof Intl[constructor] === "function") { |
| constructors[constructors.length] = constructor; |
| } |
| }); |
| |
| constructors.forEach(function (constructor) { |
| var Constructor = Intl[constructor]; |
| try { |
| f(Constructor); |
| } catch (e) { |
| e.message += " (Testing with " + constructor + ".)"; |
| throw e; |
| } |
| }); |
| } |
| |
| |
| /** |
| * Taints a named data property of the given object by installing |
| * a setter that throws an exception. |
| * @param {object} obj the object whose data property to taint |
| * @param {string} property the property to taint |
| */ |
| function taintDataProperty(obj, property) { |
| Object.defineProperty(obj, property, { |
| set: function(value) { |
| $ERROR("Client code can adversely affect behavior: setter for " + property + "."); |
| }, |
| enumerable: false, |
| configurable: true |
| }); |
| } |
| |
| |
| /** |
| * Taints a named method of the given object by replacing it with a function |
| * that throws an exception. |
| * @param {object} obj the object whose method to taint |
| * @param {string} property the name of the method to taint |
| */ |
| function taintMethod(obj, property) { |
| Object.defineProperty(obj, property, { |
| value: function() { |
| $ERROR("Client code can adversely affect behavior: method " + property + "."); |
| }, |
| writable: true, |
| enumerable: false, |
| configurable: true |
| }); |
| } |
| |
| |
| /** |
| * Taints the given properties (and similarly named properties) by installing |
| * setters on Object.prototype that throw exceptions. |
| * @param {Array} properties an array of property names to taint |
| */ |
| function taintProperties(properties) { |
| properties.forEach(function (property) { |
| var adaptedProperties = [property, "__" + property, "_" + property, property + "_", property + "__"]; |
| adaptedProperties.forEach(function (property) { |
| taintDataProperty(Object.prototype, property); |
| }); |
| }); |
| } |
| |
| |
| /** |
| * Taints the Array object by creating a setter for the property "0" and |
| * replacing some key methods with functions that throw exceptions. |
| */ |
| function taintArray() { |
| taintDataProperty(Array.prototype, "0"); |
| taintMethod(Array.prototype, "indexOf"); |
| taintMethod(Array.prototype, "join"); |
| taintMethod(Array.prototype, "push"); |
| taintMethod(Array.prototype, "slice"); |
| taintMethod(Array.prototype, "sort"); |
| } |
| |
| |
| /** |
| * Gets locale support info for the given constructor object, which must be one |
| * of Intl constructors. |
| * @param {object} Constructor the constructor for which to get locale support info |
| * @return {object} locale support info with the following properties: |
| * supported: array of fully supported language tags |
| * byFallback: array of language tags that are supported through fallbacks |
| * unsupported: array of unsupported language tags |
| */ |
| function getLocaleSupportInfo(Constructor) { |
| var languages = ["zh", "es", "en", "hi", "ur", "ar", "ja", "pa"]; |
| var scripts = ["Latn", "Hans", "Deva", "Arab", "Jpan", "Hant", "Guru"]; |
| var countries = ["CN", "IN", "US", "PK", "JP", "TW", "HK", "SG", "419"]; |
| |
| var allTags = []; |
| var i, j, k; |
| var language, script, country; |
| for (i = 0; i < languages.length; i++) { |
| language = languages[i]; |
| allTags.push(language); |
| for (j = 0; j < scripts.length; j++) { |
| script = scripts[j]; |
| allTags.push(language + "-" + script); |
| for (k = 0; k < countries.length; k++) { |
| country = countries[k]; |
| allTags.push(language + "-" + script + "-" + country); |
| } |
| } |
| for (k = 0; k < countries.length; k++) { |
| country = countries[k]; |
| allTags.push(language + "-" + country); |
| } |
| } |
| |
| var supported = []; |
| var byFallback = []; |
| var unsupported = []; |
| for (i = 0; i < allTags.length; i++) { |
| var request = allTags[i]; |
| var result = new Constructor([request], {localeMatcher: "lookup"}).resolvedOptions().locale; |
| if (request === result) { |
| supported.push(request); |
| } else if (request.indexOf(result) === 0) { |
| byFallback.push(request); |
| } else { |
| unsupported.push(request); |
| } |
| } |
| |
| return { |
| supported: supported, |
| byFallback: byFallback, |
| unsupported: unsupported |
| }; |
| } |
| |
| |
| /** |
| * Returns an array of strings for which IsStructurallyValidLanguageTag() returns false |
| */ |
| function getInvalidLanguageTags() { |
| var invalidLanguageTags = [ |
| "", // empty tag |
| "i", // singleton alone |
| "x", // private use without subtag |
| "u", // extension singleton in first place |
| "419", // region code in first place |
| "u-nu-latn-cu-bob", // extension sequence without language |
| "hans-cmn-cn", // "hans" could theoretically be a 4-letter language code, |
| // but those can't be followed by extlang codes. |
| "cmn-hans-cn-u-u", // duplicate singleton |
| "cmn-hans-cn-t-u-ca-u", // duplicate singleton |
| "de-gregory-gregory", // duplicate variant |
| "*", // language range |
| "de-*", // language range |
| "中文", // non-ASCII letters |
| "en-ß", // non-ASCII letters |
| "ıd", // non-ASCII letters |
| "es-Latn-latn", // two scripts |
| "pl-PL-pl", // two regions |
| "u-ca-gregory", // extension in first place |
| "de-1996-1996", // duplicate numeric variant |
| "pt-u-ca-gregory-u-nu-latn", // duplicate singleton subtag |
| |
| // Invalid tags starting with: https://github.com/tc39/ecma402/pull/289 |
| "no-nyn", // regular grandfathered in BCP47, but invalid in UTS35 |
| "i-klingon", // irregular grandfathered in BCP47, but invalid in UTS35 |
| "zh-hak-CN", // language with extlang in BCP47, but invalid in UTS35 |
| "sgn-ils", // language with extlang in BCP47, but invalid in UTS35 |
| "x-foo", // privateuse-only in BCP47, but invalid in UTS35 |
| "x-en-US-12345", // more privateuse-only variants. |
| "x-12345-12345-en-US", |
| "x-en-US-12345-12345", |
| "x-en-u-foo", |
| "x-en-u-foo-u-bar", |
| "x-u-foo", |
| |
| // underscores in different parts of the language tag |
| "de_DE", |
| "DE_de", |
| "cmn_Hans", |
| "cmn-hans_cn", |
| "es_419", |
| "es-419-u-nu-latn-cu_bob", |
| "i_klingon", |
| "cmn-hans-cn-t-ca-u-ca-x_t-u", |
| "enochian_enochian", |
| "de-gregory_u-ca-gregory", |
| |
| "en\u0000", // null-terminator sequence |
| " en", // leading whitespace |
| "en ", // trailing whitespace |
| "it-IT-Latn", // country before script tag |
| "de-u", // incomplete Unicode extension sequences |
| "de-u-", |
| "de-u-ca-", |
| "de-u-ca-gregory-", |
| "si-x", // incomplete private-use tags |
| "x-", |
| "x-y-", |
| ]; |
| |
| // make sure the data above is correct |
| for (var i = 0; i < invalidLanguageTags.length; ++i) { |
| var invalidTag = invalidLanguageTags[i]; |
| assert( |
| !isCanonicalizedStructurallyValidLanguageTag(invalidTag), |
| "Test data \"" + invalidTag + "\" is a canonicalized and structurally valid language tag." |
| ); |
| } |
| |
| return invalidLanguageTags; |
| } |
| |
| |
| /** |
| * @description Tests whether locale is a String value representing a |
| * structurally valid and canonicalized BCP 47 language tag, as defined in |
| * sections 6.2.2 and 6.2.3 of the ECMAScript Internationalization API |
| * Specification. |
| * @param {String} locale the string to be tested. |
| * @result {Boolean} whether the test succeeded. |
| */ |
| function isCanonicalizedStructurallyValidLanguageTag(locale) { |
| |
| /** |
| * Regular expression defining Unicode BCP 47 Locale Identifiers. |
| * |
| * Spec: https://unicode.org/reports/tr35/#Unicode_locale_identifier |
| */ |
| var alpha = "[a-z]", |
| digit = "[0-9]", |
| alphanum = "(" + alpha + "|" + digit + ")", |
| variant = "(" + alphanum + "{5,8}|(" + digit + alphanum + "{3}))", |
| region = "(" + alpha + "{2}|" + digit + "{3})", |
| script = "(" + alpha + "{4})", |
| language = "(" + alpha + "{2,3}|" + alpha + "{5,8})", |
| privateuse = "(x(-[a-z0-9]{1,8})+)", |
| singleton = "(" + digit + "|[a-wy-z])", |
| attribute= "(" + alphanum + "{3,8})", |
| keyword = "(" + alphanum + alpha + "(-" + alphanum + "{3,8})*)", |
| unicode_locale_extensions = "(u((-" + keyword + ")+|((-" + attribute + ")+(-" + keyword + ")*)))", |
| tlang = "(" + language + "(-" + script + ")?(-" + region + ")?(-" + variant + ")*)", |
| tfield = "(" + alpha + digit + "(-" + alphanum + "{3,8})+)", |
| transformed_extensions = "(t((-" + tlang + "(-" + tfield + ")*)|(-" + tfield + ")+))", |
| other_singleton = "(" + digit + "|[a-sv-wy-z])", |
| other_extensions = "(" + other_singleton + "(-" + alphanum + "{2,8})+)", |
| extension = "(" + unicode_locale_extensions + "|" + transformed_extensions + "|" + other_extensions + ")", |
| locale_id = language + "(-" + script + ")?(-" + region + ")?(-" + variant + ")*(-" + extension + ")*(-" + privateuse + ")?", |
| languageTag = "^(" + locale_id + ")$", |
| languageTagRE = new RegExp(languageTag, "i"); |
| |
| var duplicateSingleton = "-" + singleton + "-(.*-)?\\1(?!" + alphanum + ")", |
| duplicateSingletonRE = new RegExp(duplicateSingleton, "i"), |
| duplicateVariant = "(" + alphanum + "{2,8}-)+" + variant + "-(" + alphanum + "{2,8}-)*\\3(?!" + alphanum + ")", |
| duplicateVariantRE = new RegExp(duplicateVariant, "i"); |
| |
| |
| /** |
| * Verifies that the given string is a well-formed Unicode BCP 47 Locale Identifier |
| * with no duplicate variant or singleton subtags. |
| * |
| * Spec: ECMAScript Internationalization API Specification, draft, 6.2.2. |
| */ |
| function isStructurallyValidLanguageTag(locale) { |
| if (!languageTagRE.test(locale)) { |
| return false; |
| } |
| locale = locale.split(/-x-/)[0]; |
| return !duplicateSingletonRE.test(locale) && !duplicateVariantRE.test(locale); |
| } |
| |
| |
| /** |
| * Mappings from complete tags to preferred values. |
| * |
| * Spec: http://unicode.org/reports/tr35/#Identifiers |
| * Version: CLDR, version 35 |
| */ |
| var __tagMappings = { |
| // property names must be in lower case; values in canonical form |
| |
| "art-lojban": "jbo", |
| "cel-gaulish": "xtg-x-cel-gaulish", |
| "zh-guoyu": "zh", |
| "zh-hakka": "hak", |
| "zh-xiang": "hsn", |
| }; |
| |
| |
| /** |
| * Mappings from language subtags to preferred values. |
| * |
| * Spec: http://unicode.org/reports/tr35/#Identifiers |
| * Version: CLDR, version 35 |
| */ |
| var __languageMappings = { |
| // property names and values must be in canonical case |
| |
| "aam": "aas", |
| "aar": "aa", |
| "abk": "ab", |
| "adp": "dz", |
| "afr": "af", |
| "aju": "jrb", |
| "aka": "ak", |
| "alb": "sq", |
| "als": "sq", |
| "amh": "am", |
| "ara": "ar", |
| "arb": "ar", |
| "arg": "an", |
| "arm": "hy", |
| "asm": "as", |
| "aue": "ktz", |
| "ava": "av", |
| "ave": "ae", |
| "aym": "ay", |
| "ayr": "ay", |
| "ayx": "nun", |
| "aze": "az", |
| "azj": "az", |
| "bak": "ba", |
| "bam": "bm", |
| "baq": "eu", |
| "bcc": "bal", |
| "bcl": "bik", |
| "bel": "be", |
| "ben": "bn", |
| "bgm": "bcg", |
| "bh": "bho", |
| "bih": "bho", |
| "bis": "bi", |
| "bjd": "drl", |
| "bod": "bo", |
| "bos": "bs", |
| "bre": "br", |
| "bul": "bg", |
| "bur": "my", |
| "bxk": "luy", |
| "bxr": "bua", |
| "cat": "ca", |
| "ccq": "rki", |
| "ces": "cs", |
| "cha": "ch", |
| "che": "ce", |
| "chi": "zh", |
| "chu": "cu", |
| "chv": "cv", |
| "cjr": "mom", |
| "cka": "cmr", |
| "cld": "syr", |
| "cmk": "xch", |
| "cmn": "zh", |
| "cor": "kw", |
| "cos": "co", |
| "coy": "pij", |
| "cqu": "quh", |
| "cre": "cr", |
| "cwd": "cr", |
| "cym": "cy", |
| "cze": "cs", |
| "dan": "da", |
| "deu": "de", |
| "dgo": "doi", |
| "dhd": "mwr", |
| "dik": "din", |
| "diq": "zza", |
| "div": "dv", |
| "drh": "mn", |
| "dut": "nl", |
| "dzo": "dz", |
| "ekk": "et", |
| "ell": "el", |
| "emk": "man", |
| "eng": "en", |
| "epo": "eo", |
| "esk": "ik", |
| "est": "et", |
| "eus": "eu", |
| "ewe": "ee", |
| "fao": "fo", |
| "fas": "fa", |
| "fat": "ak", |
| "fij": "fj", |
| "fin": "fi", |
| "fra": "fr", |
| "fre": "fr", |
| "fry": "fy", |
| "fuc": "ff", |
| "ful": "ff", |
| "gav": "dev", |
| "gaz": "om", |
| "gbo": "grb", |
| "geo": "ka", |
| "ger": "de", |
| "gfx": "vaj", |
| "ggn": "gvr", |
| "gla": "gd", |
| "gle": "ga", |
| "glg": "gl", |
| "glv": "gv", |
| "gno": "gon", |
| "gre": "el", |
| "grn": "gn", |
| "gti": "nyc", |
| "gug": "gn", |
| "guj": "gu", |
| "guv": "duz", |
| "gya": "gba", |
| "hat": "ht", |
| "hau": "ha", |
| "hdn": "hai", |
| "hea": "hmn", |
| "heb": "he", |
| "her": "hz", |
| "him": "srx", |
| "hin": "hi", |
| "hmo": "ho", |
| "hrr": "jal", |
| "hrv": "hr", |
| "hun": "hu", |
| "hye": "hy", |
| "ibi": "opa", |
| "ibo": "ig", |
| "ice": "is", |
| "ido": "io", |
| "iii": "ii", |
| "ike": "iu", |
| "iku": "iu", |
| "ile": "ie", |
| "ilw": "gal", |
| "in": "id", |
| "ina": "ia", |
| "ind": "id", |
| "ipk": "ik", |
| "isl": "is", |
| "ita": "it", |
| "iw": "he", |
| "jav": "jv", |
| "jeg": "oyb", |
| "ji": "yi", |
| "jpn": "ja", |
| "jw": "jv", |
| "kal": "kl", |
| "kan": "kn", |
| "kas": "ks", |
| "kat": "ka", |
| "kau": "kr", |
| "kaz": "kk", |
| "kgc": "tdf", |
| "kgh": "kml", |
| "khk": "mn", |
| "khm": "km", |
| "kik": "ki", |
| "kin": "rw", |
| "kir": "ky", |
| "kmr": "ku", |
| "knc": "kr", |
| "kng": "kg", |
| "knn": "kok", |
| "koj": "kwv", |
| "kom": "kv", |
| "kon": "kg", |
| "kor": "ko", |
| "kpv": "kv", |
| "krm": "bmf", |
| "ktr": "dtp", |
| "kua": "kj", |
| "kur": "ku", |
| "kvs": "gdj", |
| "kwq": "yam", |
| "kxe": "tvd", |
| "kzj": "dtp", |
| "kzt": "dtp", |
| "lao": "lo", |
| "lat": "la", |
| "lav": "lv", |
| "lbk": "bnc", |
| "lii": "raq", |
| "lim": "li", |
| "lin": "ln", |
| "lit": "lt", |
| "lmm": "rmx", |
| "ltz": "lb", |
| "lub": "lu", |
| "lug": "lg", |
| "lvs": "lv", |
| "mac": "mk", |
| "mah": "mh", |
| "mal": "ml", |
| "mao": "mi", |
| "mar": "mr", |
| "may": "ms", |
| "meg": "cir", |
| "mhr": "chm", |
| "mkd": "mk", |
| "mlg": "mg", |
| "mlt": "mt", |
| "mnk": "man", |
| "mo": "ro", |
| "mol": "ro", |
| "mon": "mn", |
| "mri": "mi", |
| "msa": "ms", |
| "mst": "mry", |
| "mup": "raj", |
| "mwj": "vaj", |
| "mya": "my", |
| "myt": "mry", |
| "nad": "xny", |
| "nau": "na", |
| "nav": "nv", |
| "nbl": "nr", |
| "ncp": "kdz", |
| "nde": "nd", |
| "ndo": "ng", |
| "nep": "ne", |
| "nld": "nl", |
| "nno": "nn", |
| "nnx": "ngv", |
| "no": "nb", |
| "nob": "nb", |
| "nor": "nb", |
| "npi": "ne", |
| "nts": "pij", |
| "nya": "ny", |
| "oci": "oc", |
| "ojg": "oj", |
| "oji": "oj", |
| "ori": "or", |
| "orm": "om", |
| "ory": "or", |
| "oss": "os", |
| "oun": "vaj", |
| "pan": "pa", |
| "pbu": "ps", |
| "pcr": "adx", |
| "per": "fa", |
| "pes": "fa", |
| "pli": "pi", |
| "plt": "mg", |
| "pmc": "huw", |
| "pmu": "phr", |
| "pnb": "lah", |
| "pol": "pl", |
| "por": "pt", |
| "ppa": "bfy", |
| "ppr": "lcq", |
| "pry": "prt", |
| "pus": "ps", |
| "puz": "pub", |
| "que": "qu", |
| "quz": "qu", |
| "rmy": "rom", |
| "roh": "rm", |
| "ron": "ro", |
| "rum": "ro", |
| "run": "rn", |
| "rus": "ru", |
| "sag": "sg", |
| "san": "sa", |
| "sca": "hle", |
| "scc": "sr", |
| "scr": "hr", |
| "sin": "si", |
| "skk": "oyb", |
| "slk": "sk", |
| "slo": "sk", |
| "slv": "sl", |
| "sme": "se", |
| "smo": "sm", |
| "sna": "sn", |
| "snd": "sd", |
| "som": "so", |
| "sot": "st", |
| "spa": "es", |
| "spy": "kln", |
| "sqi": "sq", |
| "src": "sc", |
| "srd": "sc", |
| "srp": "sr", |
| "ssw": "ss", |
| "sun": "su", |
| "swa": "sw", |
| "swe": "sv", |
| "swh": "sw", |
| "tah": "ty", |
| "tam": "ta", |
| "tat": "tt", |
| "tdu": "dtp", |
| "tel": "te", |
| "tgk": "tg", |
| "tgl": "fil", |
| "tha": "th", |
| "thc": "tpo", |
| "thx": "oyb", |
| "tib": "bo", |
| "tie": "ras", |
| "tir": "ti", |
| "tkk": "twm", |
| "tl": "fil", |
| "tlw": "weo", |
| "tmp": "tyj", |
| "tne": "kak", |
| "ton": "to", |
| "tsf": "taj", |
| "tsn": "tn", |
| "tso": "ts", |
| "ttq": "tmh", |
| "tuk": "tk", |
| "tur": "tr", |
| "tw": "ak", |
| "twi": "ak", |
| "uig": "ug", |
| "ukr": "uk", |
| "umu": "del", |
| "uok": "ema", |
| "urd": "ur", |
| "uzb": "uz", |
| "uzn": "uz", |
| "ven": "ve", |
| "vie": "vi", |
| "vol": "vo", |
| "wel": "cy", |
| "wln": "wa", |
| "wol": "wo", |
| "xba": "cax", |
| "xho": "xh", |
| "xia": "acn", |
| "xkh": "waw", |
| "xpe": "kpe", |
| "xsj": "suj", |
| "xsl": "den", |
| "ybd": "rki", |
| "ydd": "yi", |
| "yid": "yi", |
| "yma": "lrr", |
| "ymt": "mtm", |
| "yor": "yo", |
| "yos": "zom", |
| "yuu": "yug", |
| "zai": "zap", |
| "zha": "za", |
| "zho": "zh", |
| "zsm": "ms", |
| "zul": "zu", |
| "zyb": "za", |
| } |
| |
| |
| /** |
| * Mappings from region subtags to preferred values. |
| * |
| * Spec: http://unicode.org/reports/tr35/#Identifiers |
| * Version: CLDR, version 35 |
| */ |
| var __regionMappings = { |
| // property names and values must be in canonical case |
| |
| "004": "AF", |
| "008": "AL", |
| "010": "AQ", |
| "012": "DZ", |
| "016": "AS", |
| "020": "AD", |
| "024": "AO", |
| "028": "AG", |
| "031": "AZ", |
| "032": "AR", |
| "036": "AU", |
| "040": "AT", |
| "044": "BS", |
| "048": "BH", |
| "050": "BD", |
| "051": "AM", |
| "052": "BB", |
| "056": "BE", |
| "060": "BM", |
| "062": "034", |
| "064": "BT", |
| "068": "BO", |
| "070": "BA", |
| "072": "BW", |
| "074": "BV", |
| "076": "BR", |
| "084": "BZ", |
| "086": "IO", |
| "090": "SB", |
| "092": "VG", |
| "096": "BN", |
| "100": "BG", |
| "104": "MM", |
| "108": "BI", |
| "112": "BY", |
| "116": "KH", |
| "120": "CM", |
| "124": "CA", |
| "132": "CV", |
| "136": "KY", |
| "140": "CF", |
| "144": "LK", |
| "148": "TD", |
| "152": "CL", |
| "156": "CN", |
| "158": "TW", |
| "162": "CX", |
| "166": "CC", |
| "170": "CO", |
| "174": "KM", |
| "175": "YT", |
| "178": "CG", |
| "180": "CD", |
| "184": "CK", |
| "188": "CR", |
| "191": "HR", |
| "192": "CU", |
| "196": "CY", |
| "203": "CZ", |
| "204": "BJ", |
| "208": "DK", |
| "212": "DM", |
| "214": "DO", |
| "218": "EC", |
| "222": "SV", |
| "226": "GQ", |
| "230": "ET", |
| "231": "ET", |
| "232": "ER", |
| "233": "EE", |
| "234": "FO", |
| "238": "FK", |
| "239": "GS", |
| "242": "FJ", |
| "246": "FI", |
| "248": "AX", |
| "249": "FR", |
| "250": "FR", |
| "254": "GF", |
| "258": "PF", |
| "260": "TF", |
| "262": "DJ", |
| "266": "GA", |
| "268": "GE", |
| "270": "GM", |
| "275": "PS", |
| "276": "DE", |
| "278": "DE", |
| "280": "DE", |
| "288": "GH", |
| "292": "GI", |
| "296": "KI", |
| "300": "GR", |
| "304": "GL", |
| "308": "GD", |
| "312": "GP", |
| "316": "GU", |
| "320": "GT", |
| "324": "GN", |
| "328": "GY", |
| "332": "HT", |
| "334": "HM", |
| "336": "VA", |
| "340": "HN", |
| "344": "HK", |
| "348": "HU", |
| "352": "IS", |
| "356": "IN", |
| "360": "ID", |
| "364": "IR", |
| "368": "IQ", |
| "372": "IE", |
| "376": "IL", |
| "380": "IT", |
| "384": "CI", |
| "388": "JM", |
| "392": "JP", |
| "398": "KZ", |
| "400": "JO", |
| "404": "KE", |
| "408": "KP", |
| "410": "KR", |
| "414": "KW", |
| "417": "KG", |
| "418": "LA", |
| "422": "LB", |
| "426": "LS", |
| "428": "LV", |
| "430": "LR", |
| "434": "LY", |
| "438": "LI", |
| "440": "LT", |
| "442": "LU", |
| "446": "MO", |
| "450": "MG", |
| "454": "MW", |
| "458": "MY", |
| "462": "MV", |
| "466": "ML", |
| "470": "MT", |
| "474": "MQ", |
| "478": "MR", |
| "480": "MU", |
| "484": "MX", |
| "492": "MC", |
| "496": "MN", |
| "498": "MD", |
| "499": "ME", |
| "500": "MS", |
| "504": "MA", |
| "508": "MZ", |
| "512": "OM", |
| "516": "NA", |
| "520": "NR", |
| "524": "NP", |
| "528": "NL", |
| "531": "CW", |
| "533": "AW", |
| "534": "SX", |
| "535": "BQ", |
| "540": "NC", |
| "548": "VU", |
| "554": "NZ", |
| "558": "NI", |
| "562": "NE", |
| "566": "NG", |
| "570": "NU", |
| "574": "NF", |
| "578": "NO", |
| "580": "MP", |
| "581": "UM", |
| "583": "FM", |
| "584": "MH", |
| "585": "PW", |
| "586": "PK", |
| "591": "PA", |
| "598": "PG", |
| "600": "PY", |
| "604": "PE", |
| "608": "PH", |
| "612": "PN", |
| "616": "PL", |
| "620": "PT", |
| "624": "GW", |
| "626": "TL", |
| "630": "PR", |
| "634": "QA", |
| "638": "RE", |
| "642": "RO", |
| "643": "RU", |
| "646": "RW", |
| "652": "BL", |
| "654": "SH", |
| "659": "KN", |
| "660": "AI", |
| "662": "LC", |
| "663": "MF", |
| "666": "PM", |
| "670": "VC", |
| "674": "SM", |
| "678": "ST", |
| "682": "SA", |
| "686": "SN", |
| "688": "RS", |
| "690": "SC", |
| "694": "SL", |
| "702": "SG", |
| "703": "SK", |
| "704": "VN", |
| "705": "SI", |
| "706": "SO", |
| "710": "ZA", |
| "716": "ZW", |
| "720": "YE", |
| "724": "ES", |
| "728": "SS", |
| "729": "SD", |
| "732": "EH", |
| "736": "SD", |
| "740": "SR", |
| "744": "SJ", |
| "748": "SZ", |
| "752": "SE", |
| "756": "CH", |
| "760": "SY", |
| "762": "TJ", |
| "764": "TH", |
| "768": "TG", |
| "772": "TK", |
| "776": "TO", |
| "780": "TT", |
| "784": "AE", |
| "788": "TN", |
| "792": "TR", |
| "795": "TM", |
| "796": "TC", |
| "798": "TV", |
| "800": "UG", |
| "804": "UA", |
| "807": "MK", |
| "818": "EG", |
| "826": "GB", |
| "830": "JE", |
| "831": "GG", |
| "832": "JE", |
| "833": "IM", |
| "834": "TZ", |
| "840": "US", |
| "850": "VI", |
| "854": "BF", |
| "858": "UY", |
| "860": "UZ", |
| "862": "VE", |
| "876": "WF", |
| "882": "WS", |
| "886": "YE", |
| "887": "YE", |
| "891": "RS", |
| "894": "ZM", |
| "958": "AA", |
| "959": "QM", |
| "960": "QN", |
| "962": "QP", |
| "963": "QQ", |
| "964": "QR", |
| "965": "QS", |
| "966": "QT", |
| "967": "EU", |
| "968": "QV", |
| "969": "QW", |
| "970": "QX", |
| "971": "QY", |
| "972": "QZ", |
| "973": "XA", |
| "974": "XB", |
| "975": "XC", |
| "976": "XD", |
| "977": "XE", |
| "978": "XF", |
| "979": "XG", |
| "980": "XH", |
| "981": "XI", |
| "982": "XJ", |
| "983": "XK", |
| "984": "XL", |
| "985": "XM", |
| "986": "XN", |
| "987": "XO", |
| "988": "XP", |
| "989": "XQ", |
| "990": "XR", |
| "991": "XS", |
| "992": "XT", |
| "993": "XU", |
| "994": "XV", |
| "995": "XW", |
| "996": "XX", |
| "997": "XY", |
| "998": "XZ", |
| "999": "ZZ", |
| "BU": "MM", |
| "CS": "RS", |
| "CT": "KI", |
| "DD": "DE", |
| "DY": "BJ", |
| "FQ": "AQ", |
| "FX": "FR", |
| "HV": "BF", |
| "JT": "UM", |
| "MI": "UM", |
| "NH": "VU", |
| "NQ": "AQ", |
| "PU": "UM", |
| "PZ": "PA", |
| "QU": "EU", |
| "RH": "ZW", |
| "TP": "TL", |
| "UK": "GB", |
| "VD": "VN", |
| "WK": "UM", |
| "YD": "YE", |
| "YU": "RS", |
| "ZR": "CD", |
| }; |
| |
| |
| /** |
| * Canonicalizes the given well-formed BCP 47 language tag, including regularized case of subtags. |
| * |
| * Spec: ECMAScript Internationalization API Specification, draft, 6.2.3. |
| * Spec: RFC 5646, section 4.5. |
| */ |
| function canonicalizeLanguageTag(locale) { |
| |
| // start with lower case for easier processing, and because most subtags will need to be lower case anyway |
| locale = locale.toLowerCase(); |
| |
| // handle mappings for complete tags |
| if (__tagMappings.hasOwnProperty(locale)) { |
| return __tagMappings[locale]; |
| } |
| |
| var subtags = locale.split("-"); |
| var i = 0; |
| |
| // handle standard part: all subtags before first variant or singleton subtag |
| var language; |
| var script; |
| var region; |
| while (i < subtags.length) { |
| var subtag = subtags[i]; |
| if (i === 0) { |
| language = subtag; |
| } else if (subtag.length === 2 || subtag.length === 3) { |
| region = subtag.toUpperCase(); |
| } else if (subtag.length === 4 && !("0" <= subtag[0] && subtag[0] <= "9")) { |
| script = subtag[0].toUpperCase() + subtag.substring(1).toLowerCase(); |
| } else { |
| break; |
| } |
| i++; |
| } |
| |
| if (__languageMappings.hasOwnProperty(language)) { |
| language = __languageMappings[language]; |
| } else { |
| // Language subtags with complex mappings, CLDR 35. |
| switch (language) { |
| case "cnr": |
| language = "sr"; |
| if (region === undefined) { |
| region = "ME"; |
| } |
| break; |
| case "drw": |
| case "prs": |
| case "tnf": |
| language = "fa"; |
| if (region === undefined) { |
| region = "AF"; |
| } |
| break; |
| case "hbs": |
| case "sh": |
| language = "sr"; |
| if (script === undefined) { |
| script = "Latn"; |
| } |
| break; |
| case "swc": |
| language = "sw"; |
| if (region === undefined) { |
| region = "CD"; |
| } |
| break; |
| } |
| } |
| |
| if (region !== undefined) { |
| if (__regionMappings.hasOwnProperty(region)) { |
| region = __regionMappings[region]; |
| } else { |
| // Region subtags with complex mappings, CLDR 35. |
| switch (region) { |
| case "172": |
| if (language === "ab" || language === "ka" || language === "os" || |
| (language === "und" && script === "Geor") || language === "xmf") { |
| region = "GE"; |
| } |
| else if (language === "az" || language === "tkr" || language === "tly" || language === "ttt") { |
| region = "AZ"; |
| } |
| else if (language === "be") { |
| region = "BY"; |
| } |
| else if (language === "crh" || language === "got" || language === "ji" || language === "rue" || |
| language === "uk" || (language === "und" && script === "Goth")) { |
| region = "UA"; |
| } |
| else if (language === "gag") { |
| region = "MD"; |
| } |
| else if (language === "hy" || (language === "und" && script === "Armn")) { |
| region = "AM"; |
| } |
| else if (language === "kaa" || language === "sog" || (language === "und" && script === "Sogd") || |
| (language === "und" && script === "Sogo") || language === "uz") { |
| region = "UZ"; |
| } |
| else if (language === "kk" || (language === "ug" && script === "Cyrl")) { |
| region = "KZ"; |
| } |
| else if (language === "ky") { |
| region = "KG"; |
| } |
| else if (language === "tg") { |
| region = "TJ"; |
| } |
| else if (language === "tk") { |
| region = "TM"; |
| } |
| else { |
| region = "RU"; |
| } |
| break; |
| case "200": |
| if (language === "sk") { |
| region = "SK"; |
| } |
| else { |
| region = "CZ"; |
| } |
| break; |
| case "530": |
| case "532": |
| case "AN": |
| if (language === "vic") { |
| region = "SX"; |
| } |
| else { |
| region = "CW"; |
| } |
| break; |
| case "536": |
| case "NT": |
| if (language === "akk" || language === "ckb" || (language === "ku" && script === "Arab") || |
| language === "mis" || language === "syr" || (language === "und" && script === "Xsux") || |
| (language === "und" && script === "Hatr") || (language === "und" && script === "Syrc")) { |
| region = "IQ"; |
| } |
| else { |
| region = "SA"; |
| } |
| break; |
| case "582": |
| case "PC": |
| if (language === "mh") { |
| region = "MH"; |
| } |
| else if (language === "pau") { |
| region = "PW"; |
| } |
| else { |
| region = "FM"; |
| } |
| break; |
| case "810": |
| case "SU": |
| if (language === "ab" || language === "ka" || language === "os" || language === "xmf" || |
| (language === "und" && script === "Geor")) { |
| region = "GE"; |
| } |
| else if (language === "az" || language === "tkr" || language === "tly" || language === "ttt") { |
| region = "AZ"; |
| } |
| else if (language === "be") { |
| region = "BY"; |
| } |
| else if (language === "crh" || language === "got" || language === "ji" || language === "rue" || |
| language === "uk" || (language === "und" && script === "Goth")) { |
| region = "UA"; |
| } |
| else if (language === "et" || language === "vro") { |
| region = "EE"; |
| } |
| else if (language === "gag") { |
| region = "MD"; |
| } |
| else if (language === "hy" || (language === "und" && script === "Armn")) { |
| region = "AM"; |
| } |
| else if (language === "kaa" || language === "sog" || (language === "und" && script === "Sogd") || |
| (language === "und" && script === "Sogo") || language === "uz") { |
| region = "UZ"; |
| } |
| else if (language === "kk" || (language === "ug" && script === "Cyrl")) { |
| region = "KZ"; |
| } |
| else if (language === "ky") { |
| region = "KG"; |
| } |
| else if (language === "lt" || language === "sgs") { |
| region = "LT"; |
| } |
| else if (language === "ltg" || language === "lv") { |
| region = "LV"; |
| } |
| else if (language === "tg") { |
| region = "TJ"; |
| } |
| else if (language === "tk") { |
| region = "TM"; |
| } |
| else { |
| region = "RU"; |
| } |
| break; |
| case "890": |
| if (language === "bs") { |
| region = "BA"; |
| } |
| else if (language === "hr") { |
| region = "HR"; |
| } |
| else if (language === "mk") { |
| region = "MK"; |
| } |
| else if (language === "sl") { |
| region = "SI"; |
| } |
| else { |
| region = "RS"; |
| } |
| break; |
| } |
| } |
| } |
| |
| // handle variants |
| var variants = []; |
| while (i < subtags.length && subtags[i].length > 1) { |
| variants.push(subtags[i]); |
| i += 1; |
| } |
| variants.sort(); |
| |
| // handle extensions |
| var extensions = []; |
| while (i < subtags.length && subtags[i] !== "x") { |
| var extensionStart = i; |
| i++; |
| while (i < subtags.length && subtags[i].length > 1) { |
| i++; |
| } |
| var extension = subtags.slice(extensionStart, i).join("-"); |
| extensions.push(extension); |
| } |
| extensions.sort(); |
| |
| // handle private use |
| var privateUse; |
| if (i < subtags.length) { |
| privateUse = subtags.slice(i).join("-"); |
| } |
| |
| // put everything back together |
| var canonical = language; |
| if (script !== undefined) { |
| canonical += "-" + script; |
| } |
| if (region !== undefined) { |
| canonical += "-" + region; |
| } |
| if (variants.length > 0) { |
| canonical += "-" + variants.join("-"); |
| } |
| if (extensions.length > 0) { |
| canonical += "-" + extensions.join("-"); |
| } |
| if (privateUse !== undefined) { |
| if (canonical.length > 0) { |
| canonical += "-" + privateUse; |
| } else { |
| canonical = privateUse; |
| } |
| } |
| |
| return canonical; |
| } |
| |
| return typeof locale === "string" && isStructurallyValidLanguageTag(locale) && |
| canonicalizeLanguageTag(locale) === locale; |
| } |
| |
| |
| /** |
| * Returns an array of error cases handled by CanonicalizeLocaleList(). |
| */ |
| function getInvalidLocaleArguments() { |
| function CustomError() {} |
| |
| var topLevelErrors = [ |
| // fails ToObject |
| [null, TypeError], |
| |
| // fails Get |
| [{ get length() { throw new CustomError(); } }, CustomError], |
| |
| // fail ToLength |
| [{ length: Symbol.toPrimitive }, TypeError], |
| [{ length: { get [Symbol.toPrimitive]() { throw new CustomError(); } } }, CustomError], |
| [{ length: { [Symbol.toPrimitive]() { throw new CustomError(); } } }, CustomError], |
| [{ length: { get valueOf() { throw new CustomError(); } } }, CustomError], |
| [{ length: { valueOf() { throw new CustomError(); } } }, CustomError], |
| [{ length: { get toString() { throw new CustomError(); } } }, CustomError], |
| [{ length: { toString() { throw new CustomError(); } } }, CustomError], |
| |
| // fail type check |
| [[undefined], TypeError], |
| [[null], TypeError], |
| [[true], TypeError], |
| [[Symbol.toPrimitive], TypeError], |
| [[1], TypeError], |
| [[0.1], TypeError], |
| [[NaN], TypeError], |
| ]; |
| |
| var invalidLanguageTags = [ |
| "", // empty tag |
| "i", // singleton alone |
| "x", // private use without subtag |
| "u", // extension singleton in first place |
| "419", // region code in first place |
| "u-nu-latn-cu-bob", // extension sequence without language |
| "hans-cmn-cn", // "hans" could theoretically be a 4-letter language code, |
| // but those can't be followed by extlang codes. |
| "abcdefghi", // overlong language |
| "cmn-hans-cn-u-u", // duplicate singleton |
| "cmn-hans-cn-t-u-ca-u", // duplicate singleton |
| "de-gregory-gregory", // duplicate variant |
| "*", // language range |
| "de-*", // language range |
| "中文", // non-ASCII letters |
| "en-ß", // non-ASCII letters |
| "ıd" // non-ASCII letters |
| ]; |
| |
| return topLevelErrors.concat( |
| invalidLanguageTags.map(tag => [tag, RangeError]), |
| invalidLanguageTags.map(tag => [[tag], RangeError]), |
| invalidLanguageTags.map(tag => [["en", tag], RangeError]), |
| ) |
| } |
| |
| /** |
| * Tests whether the named options property is correctly handled by the given constructor. |
| * @param {object} Constructor the constructor to test. |
| * @param {string} property the name of the options property to test. |
| * @param {string} type the type that values of the property are expected to have |
| * @param {Array} [values] an array of allowed values for the property. Not needed for boolean. |
| * @param {any} fallback the fallback value that the property assumes if not provided. |
| * @param {object} testOptions additional options: |
| * @param {boolean} isOptional whether support for this property is optional for implementations. |
| * @param {boolean} noReturn whether the resulting value of the property is not returned. |
| * @param {boolean} isILD whether the resulting value of the property is implementation and locale dependent. |
| * @param {object} extra additional option to pass along, properties are value -> {option: value}. |
| */ |
| function testOption(Constructor, property, type, values, fallback, testOptions) { |
| var isOptional = testOptions !== undefined && testOptions.isOptional === true; |
| var noReturn = testOptions !== undefined && testOptions.noReturn === true; |
| var isILD = testOptions !== undefined && testOptions.isILD === true; |
| |
| function addExtraOptions(options, value, testOptions) { |
| if (testOptions !== undefined && testOptions.extra !== undefined) { |
| var extra; |
| if (value !== undefined && testOptions.extra[value] !== undefined) { |
| extra = testOptions.extra[value]; |
| } else if (testOptions.extra.any !== undefined) { |
| extra = testOptions.extra.any; |
| } |
| if (extra !== undefined) { |
| Object.getOwnPropertyNames(extra).forEach(function (prop) { |
| options[prop] = extra[prop]; |
| }); |
| } |
| } |
| } |
| |
| var testValues, options, obj, expected, actual, error; |
| |
| // test that the specified values are accepted. Also add values that convert to specified values. |
| if (type === "boolean") { |
| if (values === undefined) { |
| values = [true, false]; |
| } |
| testValues = values.slice(0); |
| testValues.push(888); |
| testValues.push(0); |
| } else if (type === "string") { |
| testValues = values.slice(0); |
| testValues.push({toString: function () { return values[0]; }}); |
| } |
| testValues.forEach(function (value) { |
| options = {}; |
| options[property] = value; |
| addExtraOptions(options, value, testOptions); |
| obj = new Constructor(undefined, options); |
| if (noReturn) { |
| if (obj.resolvedOptions().hasOwnProperty(property)) { |
| $ERROR("Option property " + property + " is returned, but shouldn't be."); |
| } |
| } else { |
| actual = obj.resolvedOptions()[property]; |
| if (isILD) { |
| if (actual !== undefined && values.indexOf(actual) === -1) { |
| $ERROR("Invalid value " + actual + " returned for property " + property + "."); |
| } |
| } else { |
| if (type === "boolean") { |
| expected = Boolean(value); |
| } else if (type === "string") { |
| expected = String(value); |
| } |
| if (actual !== expected && !(isOptional && actual === undefined)) { |
| $ERROR("Option value " + value + " for property " + property + |
| " was not accepted; got " + actual + " instead."); |
| } |
| } |
| } |
| }); |
| |
| // test that invalid values are rejected |
| if (type === "string") { |
| var invalidValues = ["invalidValue", -1, null]; |
| // assume that we won't have values in caseless scripts |
| if (values[0].toUpperCase() !== values[0]) { |
| invalidValues.push(values[0].toUpperCase()); |
| } else { |
| invalidValues.push(values[0].toLowerCase()); |
| } |
| invalidValues.forEach(function (value) { |
| options = {}; |
| options[property] = value; |
| addExtraOptions(options, value, testOptions); |
| error = undefined; |
| try { |
| obj = new Constructor(undefined, options); |
| } catch (e) { |
| error = e; |
| } |
| if (error === undefined) { |
| $ERROR("Invalid option value " + value + " for property " + property + " was not rejected."); |
| } else if (error.name !== "RangeError") { |
| $ERROR("Invalid option value " + value + " for property " + property + " was rejected with wrong error " + error.name + "."); |
| } |
| }); |
| } |
| |
| // test that fallback value or another valid value is used if no options value is provided |
| if (!noReturn) { |
| options = {}; |
| addExtraOptions(options, undefined, testOptions); |
| obj = new Constructor(undefined, options); |
| actual = obj.resolvedOptions()[property]; |
| if (!(isOptional && actual === undefined)) { |
| if (fallback !== undefined) { |
| if (actual !== fallback) { |
| $ERROR("Option fallback value " + fallback + " for property " + property + |
| " was not used; got " + actual + " instead."); |
| } |
| } else { |
| if (values.indexOf(actual) === -1 && !(isILD && actual === undefined)) { |
| $ERROR("Invalid value " + actual + " returned for property " + property + "."); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Properties of the RegExp constructor that may be affected by use of regular |
| * expressions, and the default values of these properties. Properties are from |
| * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Deprecated_and_obsolete_features#RegExp_Properties |
| */ |
| var regExpProperties = ["$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9", |
| "$_", "$*", "$&", "$+", "$`", "$'", |
| "input", "lastMatch", "lastParen", "leftContext", "rightContext" |
| ]; |
| |
| var regExpPropertiesDefaultValues = (function () { |
| var values = Object.create(null); |
| regExpProperties.forEach(function (property) { |
| values[property] = RegExp[property]; |
| }); |
| return values; |
| }()); |
| |
| |
| /** |
| * Tests that executing the provided function (which may use regular expressions |
| * in its implementation) does not create or modify unwanted properties on the |
| * RegExp constructor. |
| */ |
| function testForUnwantedRegExpChanges(testFunc) { |
| regExpProperties.forEach(function (property) { |
| RegExp[property] = regExpPropertiesDefaultValues[property]; |
| }); |
| testFunc(); |
| regExpProperties.forEach(function (property) { |
| if (RegExp[property] !== regExpPropertiesDefaultValues[property]) { |
| $ERROR("RegExp has unexpected property " + property + " with value " + |
| RegExp[property] + "."); |
| } |
| }); |
| } |
| |
| |
| /** |
| * Tests whether name is a valid BCP 47 numbering system name |
| * and not excluded from use in the ECMAScript Internationalization API. |
| * @param {string} name the name to be tested. |
| * @return {boolean} whether name is a valid BCP 47 numbering system name and |
| * allowed for use in the ECMAScript Internationalization API. |
| */ |
| |
| function isValidNumberingSystem(name) { |
| |
| // source: CLDR file common/bcp47/number.xml; version CLDR 32. |
| var numberingSystems = [ |
| "adlm", |
| "ahom", |
| "arab", |
| "arabext", |
| "armn", |
| "armnlow", |
| "bali", |
| "beng", |
| "bhks", |
| "brah", |
| "cakm", |
| "cham", |
| "cyrl", |
| "deva", |
| "ethi", |
| "finance", |
| "fullwide", |
| "geor", |
| "gonm", |
| "grek", |
| "greklow", |
| "gujr", |
| "guru", |
| "hanidays", |
| "hanidec", |
| "hans", |
| "hansfin", |
| "hant", |
| "hantfin", |
| "hebr", |
| "hmng", |
| "java", |
| "jpan", |
| "jpanfin", |
| "kali", |
| "khmr", |
| "knda", |
| "lana", |
| "lanatham", |
| "laoo", |
| "latn", |
| "lepc", |
| "limb", |
| "mathbold", |
| "mathdbl", |
| "mathmono", |
| "mathsanb", |
| "mathsans", |
| "mlym", |
| "modi", |
| "mong", |
| "mroo", |
| "mtei", |
| "mymr", |
| "mymrshan", |
| "mymrtlng", |
| "native", |
| "newa", |
| "nkoo", |
| "olck", |
| "orya", |
| "osma", |
| "roman", |
| "romanlow", |
| "saur", |
| "shrd", |
| "sind", |
| "sinh", |
| "sora", |
| "sund", |
| "takr", |
| "talu", |
| "taml", |
| "tamldec", |
| "telu", |
| "thai", |
| "tirh", |
| "tibt", |
| "traditio", |
| "vaii", |
| "wara", |
| ]; |
| |
| var excluded = [ |
| "finance", |
| "native", |
| "traditio" |
| ]; |
| |
| |
| return numberingSystems.indexOf(name) !== -1 && excluded.indexOf(name) === -1; |
| } |
| |
| |
| /** |
| * Provides the digits of numbering systems with simple digit mappings, |
| * as specified in 11.3.2. |
| */ |
| |
| var numberingSystemDigits = { |
| arab: "٠١٢٣٤٥٦٧٨٩", |
| arabext: "۰۱۲۳۴۵۶۷۸۹", |
| bali: "\u1B50\u1B51\u1B52\u1B53\u1B54\u1B55\u1B56\u1B57\u1B58\u1B59", |
| beng: "০১২৩৪৫৬৭৮৯", |
| deva: "०१२३४५६७८९", |
| fullwide: "0123456789", |
| gujr: "૦૧૨૩૪૫૬૭૮૯", |
| guru: "੦੧੨੩੪੫੬੭੮੯", |
| hanidec: "〇一二三四五六七八九", |
| khmr: "០១២៣៤៥៦៧៨៩", |
| knda: "೦೧೨೩೪೫೬೭೮೯", |
| laoo: "໐໑໒໓໔໕໖໗໘໙", |
| latn: "0123456789", |
| limb: "\u1946\u1947\u1948\u1949\u194A\u194B\u194C\u194D\u194E\u194F", |
| mlym: "൦൧൨൩൪൫൬൭൮൯", |
| mong: "᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙", |
| mymr: "၀၁၂၃၄၅၆၇၈၉", |
| orya: "୦୧୨୩୪୫୬୭୮୯", |
| tamldec: "௦௧௨௩௪௫௬௭௮௯", |
| telu: "౦౧౨౩౪౫౬౭౮౯", |
| thai: "๐๑๒๓๔๕๖๗๘๙", |
| tibt: "༠༡༢༣༤༥༦༧༨༩" |
| }; |
| |
| |
| /** |
| * Tests that number formatting is handled correctly. The function checks that the |
| * digit sequences in formatted output are as specified, converted to the |
| * selected numbering system, and embedded in consistent localized patterns. |
| * @param {Array} locales the locales to be tested. |
| * @param {Array} numberingSystems the numbering systems to be tested. |
| * @param {Object} options the options to pass to Intl.NumberFormat. Options |
| * must include {useGrouping: false}, and must cause 1.1 to be formatted |
| * pre- and post-decimal digits. |
| * @param {Object} testData maps input data (in ES5 9.3.1 format) to expected output strings |
| * in unlocalized format with Western digits. |
| */ |
| |
| function testNumberFormat(locales, numberingSystems, options, testData) { |
| locales.forEach(function (locale) { |
| numberingSystems.forEach(function (numbering) { |
| var digits = numberingSystemDigits[numbering]; |
| var format = new Intl.NumberFormat([locale + "-u-nu-" + numbering], options); |
| |
| function getPatternParts(positive) { |
| var n = positive ? 1.1 : -1.1; |
| var formatted = format.format(n); |
| var oneoneRE = "([^" + digits + "]*)[" + digits + "]+([^" + digits + "]+)[" + digits + "]+([^" + digits + "]*)"; |
| var match = formatted.match(new RegExp(oneoneRE)); |
| if (match === null) { |
| $ERROR("Unexpected formatted " + n + " for " + |
| format.resolvedOptions().locale + " and options " + |
| JSON.stringify(options) + ": " + formatted); |
| } |
| return match; |
| } |
| |
| function toNumbering(raw) { |
| return raw.replace(/[0-9]/g, function (digit) { |
| return digits[digit.charCodeAt(0) - "0".charCodeAt(0)]; |
| }); |
| } |
| |
| function buildExpected(raw, patternParts) { |
| var period = raw.indexOf("."); |
| if (period === -1) { |
| return patternParts[1] + toNumbering(raw) + patternParts[3]; |
| } else { |
| return patternParts[1] + |
| toNumbering(raw.substring(0, period)) + |
| patternParts[2] + |
| toNumbering(raw.substring(period + 1)) + |
| patternParts[3]; |
| } |
| } |
| |
| if (format.resolvedOptions().numberingSystem === numbering) { |
| // figure out prefixes, infixes, suffixes for positive and negative values |
| var posPatternParts = getPatternParts(true); |
| var negPatternParts = getPatternParts(false); |
| |
| Object.getOwnPropertyNames(testData).forEach(function (input) { |
| var rawExpected = testData[input]; |
| var patternParts; |
| if (rawExpected[0] === "-") { |
| patternParts = negPatternParts; |
| rawExpected = rawExpected.substring(1); |
| } else { |
| patternParts = posPatternParts; |
| } |
| var expected = buildExpected(rawExpected, patternParts); |
| var actual = format.format(input); |
| if (actual !== expected) { |
| $ERROR("Formatted value for " + input + ", " + |
| format.resolvedOptions().locale + " and options " + |
| JSON.stringify(options) + " is " + actual + "; expected " + expected + "."); |
| } |
| }); |
| } |
| }); |
| }); |
| } |
| |
| |
| /** |
| * Return the components of date-time formats. |
| * @return {Array} an array with all date-time components. |
| */ |
| |
| function getDateTimeComponents() { |
| return ["weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName"]; |
| } |
| |
| |
| /** |
| * Return the valid values for the given date-time component, as specified |
| * by the table in section 12.1.1. |
| * @param {string} component a date-time component. |
| * @return {Array} an array with the valid values for the component. |
| */ |
| |
| function getDateTimeComponentValues(component) { |
| |
| var components = { |
| 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 result = components[component]; |
| if (result === undefined) { |
| $ERROR("Internal error: No values defined for date-time component " + component + "."); |
| } |
| return result; |
| } |
| |
| |
| /** |
| * @description Tests whether timeZone is a String value representing a |
| * structurally valid and canonicalized time zone name, as defined in |
| * sections 6.4.1 and 6.4.2 of the ECMAScript Internationalization API |
| * Specification. |
| * @param {String} timeZone the string to be tested. |
| * @result {Boolean} whether the test succeeded. |
| */ |
| |
| function isCanonicalizedStructurallyValidTimeZoneName(timeZone) { |
| /** |
| * Regular expression defining IANA Time Zone names. |
| * |
| * Spec: IANA Time Zone Database, Theory file |
| */ |
| var fileNameComponent = "(?:[A-Za-z_]|\\.(?!\\.?(?:/|$)))[A-Za-z.\\-_]{0,13}"; |
| var fileName = fileNameComponent + "(?:/" + fileNameComponent + ")*"; |
| var etcName = "(?:Etc/)?GMT[+-]\\d{1,2}"; |
| var systemVName = "SystemV/[A-Z]{3}\\d{1,2}(?:[A-Z]{3})?"; |
| var legacyName = etcName + "|" + systemVName + "|CST6CDT|EST5EDT|MST7MDT|PST8PDT|NZ"; |
| var zoneNamePattern = new RegExp("^(?:" + fileName + "|" + legacyName + ")$"); |
| |
| if (typeof timeZone !== "string") { |
| return false; |
| } |
| // 6.4.2 CanonicalizeTimeZoneName (timeZone), step 3 |
| if (timeZone === "UTC") { |
| return true; |
| } |
| // 6.4.2 CanonicalizeTimeZoneName (timeZone), step 3 |
| if (timeZone === "Etc/UTC" || timeZone === "Etc/GMT") { |
| return false; |
| } |
| return zoneNamePattern.test(timeZone); |
| } |