Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | /* Portions Copyright Norbert Lindenberg 2011-2012. */ |
michael@0 | 6 | |
michael@0 | 7 | /*global JSMSG_INTL_OBJECT_NOT_INITED: false, JSMSG_INVALID_LOCALES_ELEMENT: false, |
michael@0 | 8 | JSMSG_INVALID_LANGUAGE_TAG: false, JSMSG_INVALID_LOCALE_MATCHER: false, |
michael@0 | 9 | JSMSG_INVALID_OPTION_VALUE: false, JSMSG_INVALID_DIGITS_VALUE: false, |
michael@0 | 10 | JSMSG_INTL_OBJECT_REINITED: false, JSMSG_INVALID_CURRENCY_CODE: false, |
michael@0 | 11 | JSMSG_UNDEFINED_CURRENCY: false, JSMSG_INVALID_TIME_ZONE: false, |
michael@0 | 12 | JSMSG_DATE_NOT_FINITE: false, |
michael@0 | 13 | intl_Collator_availableLocales: false, |
michael@0 | 14 | intl_availableCollations: false, |
michael@0 | 15 | intl_CompareStrings: false, |
michael@0 | 16 | intl_NumberFormat_availableLocales: false, |
michael@0 | 17 | intl_numberingSystem: false, |
michael@0 | 18 | intl_FormatNumber: false, |
michael@0 | 19 | intl_DateTimeFormat_availableLocales: false, |
michael@0 | 20 | intl_availableCalendars: false, |
michael@0 | 21 | intl_patternForSkeleton: false, |
michael@0 | 22 | intl_FormatDateTime: false, |
michael@0 | 23 | */ |
michael@0 | 24 | |
michael@0 | 25 | /* |
michael@0 | 26 | * The Intl module specified by standard ECMA-402, |
michael@0 | 27 | * ECMAScript Internationalization API Specification. |
michael@0 | 28 | */ |
michael@0 | 29 | |
michael@0 | 30 | |
michael@0 | 31 | /********** Locales, Time Zones, and Currencies **********/ |
michael@0 | 32 | |
michael@0 | 33 | |
michael@0 | 34 | /** |
michael@0 | 35 | * Convert s to upper case, but limited to characters a-z. |
michael@0 | 36 | * |
michael@0 | 37 | * Spec: ECMAScript Internationalization API Specification, 6.1. |
michael@0 | 38 | */ |
michael@0 | 39 | function toASCIIUpperCase(s) { |
michael@0 | 40 | assert(typeof s === "string", "toASCIIUpperCase"); |
michael@0 | 41 | |
michael@0 | 42 | // String.prototype.toUpperCase may map non-ASCII characters into ASCII, |
michael@0 | 43 | // so go character by character (actually code unit by code unit, but |
michael@0 | 44 | // since we only care about ASCII characters here, that's OK). |
michael@0 | 45 | var result = ""; |
michael@0 | 46 | for (var i = 0; i < s.length; i++) { |
michael@0 | 47 | var c = s[i]; |
michael@0 | 48 | if ("a" <= c && c <= "z") |
michael@0 | 49 | c = callFunction(std_String_toUpperCase, c); |
michael@0 | 50 | result += c; |
michael@0 | 51 | } |
michael@0 | 52 | return result; |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | |
michael@0 | 56 | /** |
michael@0 | 57 | * Regular expression matching a "Unicode locale extension sequence", which the |
michael@0 | 58 | * specification defines as: "any substring of a language tag that starts with |
michael@0 | 59 | * a separator '-' and the singleton 'u' and includes the maximum sequence of |
michael@0 | 60 | * following non-singleton subtags and their preceding '-' separators." |
michael@0 | 61 | * |
michael@0 | 62 | * Alternatively, this may be defined as: the components of a language tag that |
michael@0 | 63 | * match the extension production in RFC 5646, where the singleton component is |
michael@0 | 64 | * "u". |
michael@0 | 65 | * |
michael@0 | 66 | * Spec: ECMAScript Internationalization API Specification, 6.2.1. |
michael@0 | 67 | */ |
michael@0 | 68 | var unicodeLocaleExtensionSequence = "-u(-[a-z0-9]{2,8})+"; |
michael@0 | 69 | var unicodeLocaleExtensionSequenceRE = new RegExp(unicodeLocaleExtensionSequence); |
michael@0 | 70 | |
michael@0 | 71 | |
michael@0 | 72 | /** |
michael@0 | 73 | * Removes Unicode locale extension sequences from the given language tag. |
michael@0 | 74 | */ |
michael@0 | 75 | function removeUnicodeExtensions(locale) { |
michael@0 | 76 | // Don't use std_String_replace directly with a regular expression, |
michael@0 | 77 | // as that would set RegExp statics. |
michael@0 | 78 | var extensions; |
michael@0 | 79 | while ((extensions = regexp_exec_no_statics(unicodeLocaleExtensionSequenceRE, locale)) !== null) { |
michael@0 | 80 | locale = callFunction(std_String_replace, locale, extensions[0], ""); |
michael@0 | 81 | unicodeLocaleExtensionSequenceRE.lastIndex = 0; |
michael@0 | 82 | } |
michael@0 | 83 | return locale; |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | |
michael@0 | 87 | /** |
michael@0 | 88 | * Regular expression defining BCP 47 language tags. |
michael@0 | 89 | * |
michael@0 | 90 | * Spec: RFC 5646 section 2.1. |
michael@0 | 91 | */ |
michael@0 | 92 | var languageTagRE = (function () { |
michael@0 | 93 | // RFC 5234 section B.1 |
michael@0 | 94 | // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z |
michael@0 | 95 | var ALPHA = "[a-zA-Z]"; |
michael@0 | 96 | // DIGIT = %x30-39 |
michael@0 | 97 | // ; 0-9 |
michael@0 | 98 | var DIGIT = "[0-9]"; |
michael@0 | 99 | |
michael@0 | 100 | // RFC 5646 section 2.1 |
michael@0 | 101 | // alphanum = (ALPHA / DIGIT) ; letters and numbers |
michael@0 | 102 | var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; |
michael@0 | 103 | // regular = "art-lojban" ; these tags match the 'langtag' |
michael@0 | 104 | // / "cel-gaulish" ; production, but their subtags |
michael@0 | 105 | // / "no-bok" ; are not extended language |
michael@0 | 106 | // / "no-nyn" ; or variant subtags: their meaning |
michael@0 | 107 | // / "zh-guoyu" ; is defined by their registration |
michael@0 | 108 | // / "zh-hakka" ; and all of these are deprecated |
michael@0 | 109 | // / "zh-min" ; in favor of a more modern |
michael@0 | 110 | // / "zh-min-nan" ; subtag or sequence of subtags |
michael@0 | 111 | // / "zh-xiang" |
michael@0 | 112 | var regular = "(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)"; |
michael@0 | 113 | // irregular = "en-GB-oed" ; irregular tags do not match |
michael@0 | 114 | // / "i-ami" ; the 'langtag' production and |
michael@0 | 115 | // / "i-bnn" ; would not otherwise be |
michael@0 | 116 | // / "i-default" ; considered 'well-formed' |
michael@0 | 117 | // / "i-enochian" ; These tags are all valid, |
michael@0 | 118 | // / "i-hak" ; but most are deprecated |
michael@0 | 119 | // / "i-klingon" ; in favor of more modern |
michael@0 | 120 | // / "i-lux" ; subtags or subtag |
michael@0 | 121 | // / "i-mingo" ; combination |
michael@0 | 122 | // / "i-navajo" |
michael@0 | 123 | // / "i-pwn" |
michael@0 | 124 | // / "i-tao" |
michael@0 | 125 | // / "i-tay" |
michael@0 | 126 | // / "i-tsu" |
michael@0 | 127 | // / "sgn-BE-FR" |
michael@0 | 128 | // / "sgn-BE-NL" |
michael@0 | 129 | // / "sgn-CH-DE" |
michael@0 | 130 | var irregular = "(?:en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)"; |
michael@0 | 131 | // grandfathered = irregular ; non-redundant tags registered |
michael@0 | 132 | // / regular ; during the RFC 3066 era |
michael@0 | 133 | var grandfathered = "(?:" + irregular + "|" + regular + ")"; |
michael@0 | 134 | // privateuse = "x" 1*("-" (1*8alphanum)) |
michael@0 | 135 | var privateuse = "(?:x(?:-[a-z0-9]{1,8})+)"; |
michael@0 | 136 | // singleton = DIGIT ; 0 - 9 |
michael@0 | 137 | // / %x41-57 ; A - W |
michael@0 | 138 | // / %x59-5A ; Y - Z |
michael@0 | 139 | // / %x61-77 ; a - w |
michael@0 | 140 | // / %x79-7A ; y - z |
michael@0 | 141 | var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])"; |
michael@0 | 142 | // extension = singleton 1*("-" (2*8alphanum)) |
michael@0 | 143 | var extension = "(?:" + singleton + "(?:-" + alphanum + "{2,8})+)"; |
michael@0 | 144 | // variant = 5*8alphanum ; registered variants |
michael@0 | 145 | // / (DIGIT 3alphanum) |
michael@0 | 146 | var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))"; |
michael@0 | 147 | // region = 2ALPHA ; ISO 3166-1 code |
michael@0 | 148 | // / 3DIGIT ; UN M.49 code |
michael@0 | 149 | var region = "(?:" + ALPHA + "{2}|" + DIGIT + "{3})"; |
michael@0 | 150 | // script = 4ALPHA ; ISO 15924 code |
michael@0 | 151 | var script = "(?:" + ALPHA + "{4})"; |
michael@0 | 152 | // extlang = 3ALPHA ; selected ISO 639 codes |
michael@0 | 153 | // *2("-" 3ALPHA) ; permanently reserved |
michael@0 | 154 | var extlang = "(?:" + ALPHA + "{3}(?:-" + ALPHA + "{3}){0,2})"; |
michael@0 | 155 | // language = 2*3ALPHA ; shortest ISO 639 code |
michael@0 | 156 | // ["-" extlang] ; sometimes followed by |
michael@0 | 157 | // ; extended language subtags |
michael@0 | 158 | // / 4ALPHA ; or reserved for future use |
michael@0 | 159 | // / 5*8ALPHA ; or registered language subtag |
michael@0 | 160 | var language = "(?:" + ALPHA + "{2,3}(?:-" + extlang + ")?|" + ALPHA + "{4}|" + ALPHA + "{5,8})"; |
michael@0 | 161 | // langtag = language |
michael@0 | 162 | // ["-" script] |
michael@0 | 163 | // ["-" region] |
michael@0 | 164 | // *("-" variant) |
michael@0 | 165 | // *("-" extension) |
michael@0 | 166 | // ["-" privateuse] |
michael@0 | 167 | var langtag = language + "(?:-" + script + ")?(?:-" + region + ")?(?:-" + |
michael@0 | 168 | variant + ")*(?:-" + extension + ")*(?:-" + privateuse + ")?"; |
michael@0 | 169 | // Language-Tag = langtag ; normal language tags |
michael@0 | 170 | // / privateuse ; private use tag |
michael@0 | 171 | // / grandfathered ; grandfathered tags |
michael@0 | 172 | var languageTag = "^(?:" + langtag + "|" + privateuse + "|" + grandfathered + ")$"; |
michael@0 | 173 | |
michael@0 | 174 | // Language tags are case insensitive (RFC 5646 section 2.1.1). |
michael@0 | 175 | return new RegExp(languageTag, "i"); |
michael@0 | 176 | }()); |
michael@0 | 177 | |
michael@0 | 178 | |
michael@0 | 179 | var duplicateVariantRE = (function () { |
michael@0 | 180 | // RFC 5234 section B.1 |
michael@0 | 181 | // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z |
michael@0 | 182 | var ALPHA = "[a-zA-Z]"; |
michael@0 | 183 | // DIGIT = %x30-39 |
michael@0 | 184 | // ; 0-9 |
michael@0 | 185 | var DIGIT = "[0-9]"; |
michael@0 | 186 | |
michael@0 | 187 | // RFC 5646 section 2.1 |
michael@0 | 188 | // alphanum = (ALPHA / DIGIT) ; letters and numbers |
michael@0 | 189 | var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; |
michael@0 | 190 | // variant = 5*8alphanum ; registered variants |
michael@0 | 191 | // / (DIGIT 3alphanum) |
michael@0 | 192 | var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))"; |
michael@0 | 193 | |
michael@0 | 194 | // Match a langtag that contains a duplicate variant. |
michael@0 | 195 | var duplicateVariant = |
michael@0 | 196 | // Match everything in a langtag prior to any variants, and maybe some |
michael@0 | 197 | // of the variants as well (which makes this pattern inefficient but |
michael@0 | 198 | // not wrong, for our purposes); |
michael@0 | 199 | "(?:" + alphanum + "{2,8}-)+" + |
michael@0 | 200 | // a variant, parenthesised so that we can refer back to it later; |
michael@0 | 201 | "(" + variant + ")-" + |
michael@0 | 202 | // zero or more subtags at least two characters long (thus stopping |
michael@0 | 203 | // before extension and privateuse components); |
michael@0 | 204 | "(?:" + alphanum + "{2,8}-)*" + |
michael@0 | 205 | // and the same variant again |
michael@0 | 206 | "\\1" + |
michael@0 | 207 | // ...but not followed by any characters that would turn it into a |
michael@0 | 208 | // different subtag. |
michael@0 | 209 | "(?!" + alphanum + ")"; |
michael@0 | 210 | |
michael@0 | 211 | // Language tags are case insensitive (RFC 5646 section 2.1.1), but for |
michael@0 | 212 | // this regular expression that's covered by having its character classes |
michael@0 | 213 | // list both upper- and lower-case characters. |
michael@0 | 214 | return new RegExp(duplicateVariant); |
michael@0 | 215 | }()); |
michael@0 | 216 | |
michael@0 | 217 | |
michael@0 | 218 | var duplicateSingletonRE = (function () { |
michael@0 | 219 | // RFC 5234 section B.1 |
michael@0 | 220 | // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z |
michael@0 | 221 | var ALPHA = "[a-zA-Z]"; |
michael@0 | 222 | // DIGIT = %x30-39 |
michael@0 | 223 | // ; 0-9 |
michael@0 | 224 | var DIGIT = "[0-9]"; |
michael@0 | 225 | |
michael@0 | 226 | // RFC 5646 section 2.1 |
michael@0 | 227 | // alphanum = (ALPHA / DIGIT) ; letters and numbers |
michael@0 | 228 | var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; |
michael@0 | 229 | // singleton = DIGIT ; 0 - 9 |
michael@0 | 230 | // / %x41-57 ; A - W |
michael@0 | 231 | // / %x59-5A ; Y - Z |
michael@0 | 232 | // / %x61-77 ; a - w |
michael@0 | 233 | // / %x79-7A ; y - z |
michael@0 | 234 | var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])"; |
michael@0 | 235 | |
michael@0 | 236 | // Match a langtag that contains a duplicate singleton. |
michael@0 | 237 | var duplicateSingleton = |
michael@0 | 238 | // Match a singleton subtag, parenthesised so that we can refer back to |
michael@0 | 239 | // it later; |
michael@0 | 240 | "-(" + singleton + ")-" + |
michael@0 | 241 | // then zero or more subtags; |
michael@0 | 242 | "(?:" + alphanum + "+-)*" + |
michael@0 | 243 | // and the same singleton again |
michael@0 | 244 | "\\1" + |
michael@0 | 245 | // ...but not followed by any characters that would turn it into a |
michael@0 | 246 | // different subtag. |
michael@0 | 247 | "(?!" + alphanum + ")"; |
michael@0 | 248 | |
michael@0 | 249 | // Language tags are case insensitive (RFC 5646 section 2.1.1), but for |
michael@0 | 250 | // this regular expression that's covered by having its character classes |
michael@0 | 251 | // list both upper- and lower-case characters. |
michael@0 | 252 | return new RegExp(duplicateSingleton); |
michael@0 | 253 | }()); |
michael@0 | 254 | |
michael@0 | 255 | |
michael@0 | 256 | /** |
michael@0 | 257 | * Verifies that the given string is a well-formed BCP 47 language tag |
michael@0 | 258 | * with no duplicate variant or singleton subtags. |
michael@0 | 259 | * |
michael@0 | 260 | * Spec: ECMAScript Internationalization API Specification, 6.2.2. |
michael@0 | 261 | */ |
michael@0 | 262 | function IsStructurallyValidLanguageTag(locale) { |
michael@0 | 263 | assert(typeof locale === "string", "IsStructurallyValidLanguageTag"); |
michael@0 | 264 | if (!regexp_test_no_statics(languageTagRE, locale)) |
michael@0 | 265 | return false; |
michael@0 | 266 | |
michael@0 | 267 | // Before checking for duplicate variant or singleton subtags with |
michael@0 | 268 | // regular expressions, we have to get private use subtag sequences |
michael@0 | 269 | // out of the picture. |
michael@0 | 270 | if (callFunction(std_String_startsWith, locale, "x-")) |
michael@0 | 271 | return true; |
michael@0 | 272 | var pos = callFunction(std_String_indexOf, locale, "-x-"); |
michael@0 | 273 | if (pos !== -1) |
michael@0 | 274 | locale = callFunction(std_String_substring, locale, 0, pos); |
michael@0 | 275 | |
michael@0 | 276 | // Check for duplicate variant or singleton subtags. |
michael@0 | 277 | return !regexp_test_no_statics(duplicateVariantRE, locale) && |
michael@0 | 278 | !regexp_test_no_statics(duplicateSingletonRE, locale); |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | |
michael@0 | 282 | /** |
michael@0 | 283 | * Canonicalizes the given structurally valid BCP 47 language tag, including |
michael@0 | 284 | * regularized case of subtags. For example, the language tag |
michael@0 | 285 | * Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE, where |
michael@0 | 286 | * |
michael@0 | 287 | * Zh ; 2*3ALPHA |
michael@0 | 288 | * -NAN ; ["-" extlang] |
michael@0 | 289 | * -haNS ; ["-" script] |
michael@0 | 290 | * -bu ; ["-" region] |
michael@0 | 291 | * -variant2 ; *("-" variant) |
michael@0 | 292 | * -Variant1 |
michael@0 | 293 | * -u-ca-chinese ; *("-" extension) |
michael@0 | 294 | * -t-Zh-laTN |
michael@0 | 295 | * -x-PRIVATE ; ["-" privateuse] |
michael@0 | 296 | * |
michael@0 | 297 | * becomes nan-Hans-mm-variant2-variant1-t-zh-latn-u-ca-chinese-x-private |
michael@0 | 298 | * |
michael@0 | 299 | * Spec: ECMAScript Internationalization API Specification, 6.2.3. |
michael@0 | 300 | * Spec: RFC 5646, section 4.5. |
michael@0 | 301 | */ |
michael@0 | 302 | function CanonicalizeLanguageTag(locale) { |
michael@0 | 303 | assert(IsStructurallyValidLanguageTag(locale), "CanonicalizeLanguageTag"); |
michael@0 | 304 | |
michael@0 | 305 | // The input |
michael@0 | 306 | // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE" |
michael@0 | 307 | // will be used throughout this method to illustrate how it works. |
michael@0 | 308 | |
michael@0 | 309 | // Language tags are compared and processed case-insensitively, so |
michael@0 | 310 | // technically it's not necessary to adjust case. But for easier processing, |
michael@0 | 311 | // and because the canonical form for most subtags is lower case, we start |
michael@0 | 312 | // with lower case for all. |
michael@0 | 313 | // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE" -> |
michael@0 | 314 | // "zh-nan-hans-bu-variant2-variant1-u-ca-chinese-t-zh-latn-x-private" |
michael@0 | 315 | locale = callFunction(std_String_toLowerCase, locale); |
michael@0 | 316 | |
michael@0 | 317 | // Handle mappings for complete tags. |
michael@0 | 318 | if (callFunction(std_Object_hasOwnProperty, langTagMappings, locale)) |
michael@0 | 319 | return langTagMappings[locale]; |
michael@0 | 320 | |
michael@0 | 321 | var subtags = callFunction(std_String_split, locale, "-"); |
michael@0 | 322 | var i = 0; |
michael@0 | 323 | |
michael@0 | 324 | // Handle the standard part: All subtags before the first singleton or "x". |
michael@0 | 325 | // "zh-nan-hans-bu-variant2-variant1" |
michael@0 | 326 | while (i < subtags.length) { |
michael@0 | 327 | var subtag = subtags[i]; |
michael@0 | 328 | |
michael@0 | 329 | // If we reach the start of an extension sequence or private use part, |
michael@0 | 330 | // we're done with this loop. We have to check for i > 0 because for |
michael@0 | 331 | // irregular language tags, such as i-klingon, the single-character |
michael@0 | 332 | // subtag "i" is not the start of an extension sequence. |
michael@0 | 333 | // In the example, we break at "u". |
michael@0 | 334 | if (subtag.length === 1 && (i > 0 || subtag === "x")) |
michael@0 | 335 | break; |
michael@0 | 336 | |
michael@0 | 337 | if (subtag.length === 4) { |
michael@0 | 338 | // 4-character subtags are script codes; their first character |
michael@0 | 339 | // needs to be capitalized. "hans" -> "Hans" |
michael@0 | 340 | subtag = callFunction(std_String_toUpperCase, subtag[0]) + |
michael@0 | 341 | callFunction(std_String_substring, subtag, 1); |
michael@0 | 342 | } else if (i !== 0 && subtag.length === 2) { |
michael@0 | 343 | // 2-character subtags that are not in initial position are region |
michael@0 | 344 | // codes; they need to be upper case. "bu" -> "BU" |
michael@0 | 345 | subtag = callFunction(std_String_toUpperCase, subtag); |
michael@0 | 346 | } |
michael@0 | 347 | if (callFunction(std_Object_hasOwnProperty, langSubtagMappings, subtag)) { |
michael@0 | 348 | // Replace deprecated subtags with their preferred values. |
michael@0 | 349 | // "BU" -> "MM" |
michael@0 | 350 | // This has to come after we capitalize region codes because |
michael@0 | 351 | // otherwise some language and region codes could be confused. |
michael@0 | 352 | // For example, "in" is an obsolete language code for Indonesian, |
michael@0 | 353 | // but "IN" is the country code for India. |
michael@0 | 354 | // Note that the script generating langSubtagMappings makes sure |
michael@0 | 355 | // that no regular subtag mapping will replace an extlang code. |
michael@0 | 356 | subtag = langSubtagMappings[subtag]; |
michael@0 | 357 | } else if (callFunction(std_Object_hasOwnProperty, extlangMappings, subtag)) { |
michael@0 | 358 | // Replace deprecated extlang subtags with their preferred values, |
michael@0 | 359 | // and remove the preceding subtag if it's a redundant prefix. |
michael@0 | 360 | // "zh-nan" -> "nan" |
michael@0 | 361 | // Note that the script generating extlangMappings makes sure that |
michael@0 | 362 | // no extlang mapping will replace a normal language code. |
michael@0 | 363 | subtag = extlangMappings[subtag].preferred; |
michael@0 | 364 | if (i === 1 && extlangMappings[subtag].prefix === subtags[0]) { |
michael@0 | 365 | callFunction(std_Array_shift, subtags); |
michael@0 | 366 | i--; |
michael@0 | 367 | } |
michael@0 | 368 | } |
michael@0 | 369 | subtags[i] = subtag; |
michael@0 | 370 | i++; |
michael@0 | 371 | } |
michael@0 | 372 | var normal = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, 0, i), "-"); |
michael@0 | 373 | |
michael@0 | 374 | // Extension sequences are sorted by their singleton characters. |
michael@0 | 375 | // "u-ca-chinese-t-zh-latn" -> "t-zh-latn-u-ca-chinese" |
michael@0 | 376 | var extensions = new List(); |
michael@0 | 377 | while (i < subtags.length && subtags[i] !== "x") { |
michael@0 | 378 | var extensionStart = i; |
michael@0 | 379 | i++; |
michael@0 | 380 | while (i < subtags.length && subtags[i].length > 1) |
michael@0 | 381 | i++; |
michael@0 | 382 | var extension = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, extensionStart, i), "-"); |
michael@0 | 383 | extensions.push(extension); |
michael@0 | 384 | } |
michael@0 | 385 | extensions.sort(); |
michael@0 | 386 | |
michael@0 | 387 | // Private use sequences are left as is. "x-private" |
michael@0 | 388 | var privateUse = ""; |
michael@0 | 389 | if (i < subtags.length) |
michael@0 | 390 | privateUse = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, i), "-"); |
michael@0 | 391 | |
michael@0 | 392 | // Put everything back together. |
michael@0 | 393 | var canonical = normal; |
michael@0 | 394 | if (extensions.length > 0) |
michael@0 | 395 | canonical += "-" + extensions.join("-"); |
michael@0 | 396 | if (privateUse.length > 0) { |
michael@0 | 397 | // Be careful of a Language-Tag that is entirely privateuse. |
michael@0 | 398 | if (canonical.length > 0) |
michael@0 | 399 | canonical += "-" + privateUse; |
michael@0 | 400 | else |
michael@0 | 401 | canonical = privateUse; |
michael@0 | 402 | } |
michael@0 | 403 | |
michael@0 | 404 | return canonical; |
michael@0 | 405 | } |
michael@0 | 406 | |
michael@0 | 407 | |
michael@0 | 408 | // mappings from some commonly used old-style language tags to current flavors |
michael@0 | 409 | // with script codes |
michael@0 | 410 | var oldStyleLanguageTagMappings = { |
michael@0 | 411 | "pa-PK": "pa-Arab-PK", |
michael@0 | 412 | "zh-CN": "zh-Hans-CN", |
michael@0 | 413 | "zh-HK": "zh-Hant-HK", |
michael@0 | 414 | "zh-SG": "zh-Hans-SG", |
michael@0 | 415 | "zh-TW": "zh-Hant-TW" |
michael@0 | 416 | }; |
michael@0 | 417 | |
michael@0 | 418 | |
michael@0 | 419 | /** |
michael@0 | 420 | * Returns the BCP 47 language tag for the host environment's current locale. |
michael@0 | 421 | * |
michael@0 | 422 | * Spec: ECMAScript Internationalization API Specification, 6.2.4. |
michael@0 | 423 | */ |
michael@0 | 424 | function DefaultLocale() { |
michael@0 | 425 | // The locale of last resort is used if none of the available locales |
michael@0 | 426 | // satisfies a request. "en-GB" is used based on the assumptions that |
michael@0 | 427 | // English is the most common second language, that both en-GB and en-US |
michael@0 | 428 | // are normally available in an implementation, and that en-GB is more |
michael@0 | 429 | // representative of the English used in other locales. |
michael@0 | 430 | var localeOfLastResort = "en-GB"; |
michael@0 | 431 | |
michael@0 | 432 | var locale = RuntimeDefaultLocale(); |
michael@0 | 433 | if (!IsStructurallyValidLanguageTag(locale)) |
michael@0 | 434 | return localeOfLastResort; |
michael@0 | 435 | |
michael@0 | 436 | locale = CanonicalizeLanguageTag(locale); |
michael@0 | 437 | if (callFunction(std_Object_hasOwnProperty, oldStyleLanguageTagMappings, locale)) |
michael@0 | 438 | locale = oldStyleLanguageTagMappings[locale]; |
michael@0 | 439 | |
michael@0 | 440 | if (!(collatorInternalProperties.availableLocales()[locale] && |
michael@0 | 441 | numberFormatInternalProperties.availableLocales()[locale] && |
michael@0 | 442 | dateTimeFormatInternalProperties.availableLocales()[locale])) |
michael@0 | 443 | { |
michael@0 | 444 | locale = localeOfLastResort; |
michael@0 | 445 | } |
michael@0 | 446 | return locale; |
michael@0 | 447 | } |
michael@0 | 448 | |
michael@0 | 449 | |
michael@0 | 450 | /** |
michael@0 | 451 | * Verifies that the given string is a well-formed ISO 4217 currency code. |
michael@0 | 452 | * |
michael@0 | 453 | * Spec: ECMAScript Internationalization API Specification, 6.3.1. |
michael@0 | 454 | */ |
michael@0 | 455 | function IsWellFormedCurrencyCode(currency) { |
michael@0 | 456 | var c = ToString(currency); |
michael@0 | 457 | var normalized = toASCIIUpperCase(c); |
michael@0 | 458 | if (normalized.length !== 3) |
michael@0 | 459 | return false; |
michael@0 | 460 | return !regexp_test_no_statics(/[^A-Z]/, normalized); |
michael@0 | 461 | } |
michael@0 | 462 | |
michael@0 | 463 | |
michael@0 | 464 | /********** Locale and Parameter Negotiation **********/ |
michael@0 | 465 | |
michael@0 | 466 | |
michael@0 | 467 | /** |
michael@0 | 468 | * Add old-style language tags without script code for locales that in current |
michael@0 | 469 | * usage would include a script subtag. Returns the availableLocales argument |
michael@0 | 470 | * provided. |
michael@0 | 471 | * |
michael@0 | 472 | * Spec: ECMAScript Internationalization API Specification, 9.1. |
michael@0 | 473 | */ |
michael@0 | 474 | function addOldStyleLanguageTags(availableLocales) { |
michael@0 | 475 | var oldStyleLocales = std_Object_getOwnPropertyNames(oldStyleLanguageTagMappings); |
michael@0 | 476 | for (var i = 0; i < oldStyleLocales.length; i++) { |
michael@0 | 477 | var oldStyleLocale = oldStyleLocales[i]; |
michael@0 | 478 | if (availableLocales[oldStyleLanguageTagMappings[oldStyleLocale]]) |
michael@0 | 479 | availableLocales[oldStyleLocale] = true; |
michael@0 | 480 | } |
michael@0 | 481 | return availableLocales; |
michael@0 | 482 | } |
michael@0 | 483 | |
michael@0 | 484 | |
michael@0 | 485 | /** |
michael@0 | 486 | * Canonicalizes a locale list. |
michael@0 | 487 | * |
michael@0 | 488 | * Spec: ECMAScript Internationalization API Specification, 9.2.1. |
michael@0 | 489 | */ |
michael@0 | 490 | function CanonicalizeLocaleList(locales) { |
michael@0 | 491 | if (locales === undefined) |
michael@0 | 492 | return new List(); |
michael@0 | 493 | var seen = new List(); |
michael@0 | 494 | if (typeof locales === "string") |
michael@0 | 495 | locales = [locales]; |
michael@0 | 496 | var O = ToObject(locales); |
michael@0 | 497 | var len = TO_UINT32(O.length); |
michael@0 | 498 | var k = 0; |
michael@0 | 499 | while (k < len) { |
michael@0 | 500 | // Don't call ToString(k) - SpiderMonkey is faster with integers. |
michael@0 | 501 | var kPresent = HasProperty(O, k); |
michael@0 | 502 | if (kPresent) { |
michael@0 | 503 | var kValue = O[k]; |
michael@0 | 504 | if (!(typeof kValue === "string" || IsObject(kValue))) |
michael@0 | 505 | ThrowError(JSMSG_INVALID_LOCALES_ELEMENT); |
michael@0 | 506 | var tag = ToString(kValue); |
michael@0 | 507 | if (!IsStructurallyValidLanguageTag(tag)) |
michael@0 | 508 | ThrowError(JSMSG_INVALID_LANGUAGE_TAG, tag); |
michael@0 | 509 | tag = CanonicalizeLanguageTag(tag); |
michael@0 | 510 | if (seen.indexOf(tag) === -1) |
michael@0 | 511 | seen.push(tag); |
michael@0 | 512 | } |
michael@0 | 513 | k++; |
michael@0 | 514 | } |
michael@0 | 515 | return seen; |
michael@0 | 516 | } |
michael@0 | 517 | |
michael@0 | 518 | |
michael@0 | 519 | /** |
michael@0 | 520 | * Compares a BCP 47 language tag against the locales in availableLocales |
michael@0 | 521 | * and returns the best available match. Uses the fallback |
michael@0 | 522 | * mechanism of RFC 4647, section 3.4. |
michael@0 | 523 | * |
michael@0 | 524 | * Spec: ECMAScript Internationalization API Specification, 9.2.2. |
michael@0 | 525 | * Spec: RFC 4647, section 3.4. |
michael@0 | 526 | */ |
michael@0 | 527 | function BestAvailableLocale(availableLocales, locale) { |
michael@0 | 528 | assert(IsStructurallyValidLanguageTag(locale), "invalid BestAvailableLocale locale structure"); |
michael@0 | 529 | assert(locale === CanonicalizeLanguageTag(locale), "non-canonical BestAvailableLocale locale"); |
michael@0 | 530 | assert(callFunction(std_String_indexOf, locale, "-u-") === -1, "locale shouldn't contain -u-"); |
michael@0 | 531 | |
michael@0 | 532 | var candidate = locale; |
michael@0 | 533 | while (true) { |
michael@0 | 534 | if (availableLocales[candidate]) |
michael@0 | 535 | return candidate; |
michael@0 | 536 | var pos = callFunction(std_String_lastIndexOf, candidate, "-"); |
michael@0 | 537 | if (pos === -1) |
michael@0 | 538 | return undefined; |
michael@0 | 539 | if (pos >= 2 && candidate[pos - 2] === "-") |
michael@0 | 540 | pos -= 2; |
michael@0 | 541 | candidate = callFunction(std_String_substring, candidate, 0, pos); |
michael@0 | 542 | } |
michael@0 | 543 | } |
michael@0 | 544 | |
michael@0 | 545 | |
michael@0 | 546 | /** |
michael@0 | 547 | * Compares a BCP 47 language priority list against the set of locales in |
michael@0 | 548 | * availableLocales and determines the best available language to meet the |
michael@0 | 549 | * request. Options specified through Unicode extension subsequences are |
michael@0 | 550 | * ignored in the lookup, but information about such subsequences is returned |
michael@0 | 551 | * separately. |
michael@0 | 552 | * |
michael@0 | 553 | * This variant is based on the Lookup algorithm of RFC 4647 section 3.4. |
michael@0 | 554 | * |
michael@0 | 555 | * Spec: ECMAScript Internationalization API Specification, 9.2.3. |
michael@0 | 556 | * Spec: RFC 4647, section 3.4. |
michael@0 | 557 | */ |
michael@0 | 558 | function LookupMatcher(availableLocales, requestedLocales) { |
michael@0 | 559 | var i = 0; |
michael@0 | 560 | var len = requestedLocales.length; |
michael@0 | 561 | var availableLocale; |
michael@0 | 562 | var locale, noExtensionsLocale; |
michael@0 | 563 | while (i < len && availableLocale === undefined) { |
michael@0 | 564 | locale = requestedLocales[i]; |
michael@0 | 565 | noExtensionsLocale = removeUnicodeExtensions(locale); |
michael@0 | 566 | availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale); |
michael@0 | 567 | i++; |
michael@0 | 568 | } |
michael@0 | 569 | |
michael@0 | 570 | var result = new Record(); |
michael@0 | 571 | if (availableLocale !== undefined) { |
michael@0 | 572 | result.locale = availableLocale; |
michael@0 | 573 | if (locale !== noExtensionsLocale) { |
michael@0 | 574 | var extensionMatch = regexp_exec_no_statics(unicodeLocaleExtensionSequenceRE, locale); |
michael@0 | 575 | var extension = extensionMatch[0]; |
michael@0 | 576 | var extensionIndex = extensionMatch.index; |
michael@0 | 577 | result.extension = extension; |
michael@0 | 578 | result.extensionIndex = extensionIndex; |
michael@0 | 579 | } |
michael@0 | 580 | } else { |
michael@0 | 581 | result.locale = DefaultLocale(); |
michael@0 | 582 | } |
michael@0 | 583 | return result; |
michael@0 | 584 | } |
michael@0 | 585 | |
michael@0 | 586 | |
michael@0 | 587 | /** |
michael@0 | 588 | * Compares a BCP 47 language priority list against the set of locales in |
michael@0 | 589 | * availableLocales and determines the best available language to meet the |
michael@0 | 590 | * request. Options specified through Unicode extension subsequences are |
michael@0 | 591 | * ignored in the lookup, but information about such subsequences is returned |
michael@0 | 592 | * separately. |
michael@0 | 593 | * |
michael@0 | 594 | * Spec: ECMAScript Internationalization API Specification, 9.2.4. |
michael@0 | 595 | */ |
michael@0 | 596 | function BestFitMatcher(availableLocales, requestedLocales) { |
michael@0 | 597 | // this implementation doesn't have anything better |
michael@0 | 598 | return LookupMatcher(availableLocales, requestedLocales); |
michael@0 | 599 | } |
michael@0 | 600 | |
michael@0 | 601 | |
michael@0 | 602 | /** |
michael@0 | 603 | * Compares a BCP 47 language priority list against availableLocales and |
michael@0 | 604 | * determines the best available language to meet the request. Options specified |
michael@0 | 605 | * through Unicode extension subsequences are negotiated separately, taking the |
michael@0 | 606 | * caller's relevant extensions and locale data as well as client-provided |
michael@0 | 607 | * options into consideration. |
michael@0 | 608 | * |
michael@0 | 609 | * Spec: ECMAScript Internationalization API Specification, 9.2.5. |
michael@0 | 610 | */ |
michael@0 | 611 | function ResolveLocale(availableLocales, requestedLocales, options, relevantExtensionKeys, localeData) { |
michael@0 | 612 | /*jshint laxbreak: true */ |
michael@0 | 613 | |
michael@0 | 614 | // Steps 1-3. |
michael@0 | 615 | var matcher = options.localeMatcher; |
michael@0 | 616 | var r = (matcher === "lookup") |
michael@0 | 617 | ? LookupMatcher(availableLocales, requestedLocales) |
michael@0 | 618 | : BestFitMatcher(availableLocales, requestedLocales); |
michael@0 | 619 | |
michael@0 | 620 | // Step 4. |
michael@0 | 621 | var foundLocale = r.locale; |
michael@0 | 622 | |
michael@0 | 623 | // Step 5.a. |
michael@0 | 624 | var extension = r.extension; |
michael@0 | 625 | var extensionIndex, extensionSubtags, extensionSubtagsLength; |
michael@0 | 626 | |
michael@0 | 627 | // Step 5. |
michael@0 | 628 | if (extension !== undefined) { |
michael@0 | 629 | // Step 5.b. |
michael@0 | 630 | extensionIndex = r.extensionIndex; |
michael@0 | 631 | |
michael@0 | 632 | // Steps 5.d-e. |
michael@0 | 633 | extensionSubtags = callFunction(std_String_split, extension, "-"); |
michael@0 | 634 | extensionSubtagsLength = extensionSubtags.length; |
michael@0 | 635 | } |
michael@0 | 636 | |
michael@0 | 637 | // Steps 6-7. |
michael@0 | 638 | var result = new Record(); |
michael@0 | 639 | result.dataLocale = foundLocale; |
michael@0 | 640 | |
michael@0 | 641 | // Step 8. |
michael@0 | 642 | var supportedExtension = "-u"; |
michael@0 | 643 | |
michael@0 | 644 | // Steps 9-11. |
michael@0 | 645 | var i = 0; |
michael@0 | 646 | var len = relevantExtensionKeys.length; |
michael@0 | 647 | while (i < len) { |
michael@0 | 648 | // Steps 11.a-c. |
michael@0 | 649 | var key = relevantExtensionKeys[i]; |
michael@0 | 650 | |
michael@0 | 651 | // In this implementation, localeData is a function, not an object. |
michael@0 | 652 | var foundLocaleData = localeData(foundLocale); |
michael@0 | 653 | var keyLocaleData = foundLocaleData[key]; |
michael@0 | 654 | |
michael@0 | 655 | // Locale data provides default value. |
michael@0 | 656 | // Step 11.d. |
michael@0 | 657 | var value = keyLocaleData[0]; |
michael@0 | 658 | |
michael@0 | 659 | // Locale tag may override. |
michael@0 | 660 | |
michael@0 | 661 | // Step 11.e. |
michael@0 | 662 | var supportedExtensionAddition = ""; |
michael@0 | 663 | |
michael@0 | 664 | // Step 11.f is implemented by Utilities.js. |
michael@0 | 665 | |
michael@0 | 666 | var valuePos; |
michael@0 | 667 | |
michael@0 | 668 | // Step 11.g. |
michael@0 | 669 | if (extensionSubtags !== undefined) { |
michael@0 | 670 | // Step 11.g.i. |
michael@0 | 671 | var keyPos = callFunction(std_Array_indexOf, extensionSubtags, key); |
michael@0 | 672 | |
michael@0 | 673 | // Step 11.g.ii. |
michael@0 | 674 | if (keyPos !== -1) { |
michael@0 | 675 | // Step 11.g.ii.1. |
michael@0 | 676 | if (keyPos + 1 < extensionSubtagsLength && |
michael@0 | 677 | extensionSubtags[keyPos + 1].length > 2) |
michael@0 | 678 | { |
michael@0 | 679 | // Step 11.g.ii.1.a. |
michael@0 | 680 | var requestedValue = extensionSubtags[keyPos + 1]; |
michael@0 | 681 | |
michael@0 | 682 | // Step 11.g.ii.1.b. |
michael@0 | 683 | valuePos = callFunction(std_Array_indexOf, keyLocaleData, requestedValue); |
michael@0 | 684 | |
michael@0 | 685 | // Step 11.g.ii.1.c. |
michael@0 | 686 | if (valuePos !== -1) { |
michael@0 | 687 | value = requestedValue; |
michael@0 | 688 | supportedExtensionAddition = "-" + key + "-" + value; |
michael@0 | 689 | } |
michael@0 | 690 | } else { |
michael@0 | 691 | // Step 11.g.ii.2. |
michael@0 | 692 | |
michael@0 | 693 | // According to the LDML spec, if there's no type value, |
michael@0 | 694 | // and true is an allowed value, it's used. |
michael@0 | 695 | |
michael@0 | 696 | // Step 11.g.ii.2.a. |
michael@0 | 697 | valuePos = callFunction(std_Array_indexOf, keyLocaleData, "true"); |
michael@0 | 698 | |
michael@0 | 699 | // Step 11.g.ii.2.b. |
michael@0 | 700 | if (valuePos !== -1) |
michael@0 | 701 | value = "true"; |
michael@0 | 702 | } |
michael@0 | 703 | } |
michael@0 | 704 | } |
michael@0 | 705 | |
michael@0 | 706 | // Options override all. |
michael@0 | 707 | |
michael@0 | 708 | // Step 11.h.i. |
michael@0 | 709 | var optionsValue = options[key]; |
michael@0 | 710 | |
michael@0 | 711 | // Step 11.h, 11.h.ii. |
michael@0 | 712 | if (optionsValue !== undefined && |
michael@0 | 713 | callFunction(std_Array_indexOf, keyLocaleData, optionsValue) !== -1) |
michael@0 | 714 | { |
michael@0 | 715 | // Step 11.h.ii.1. |
michael@0 | 716 | if (optionsValue !== value) { |
michael@0 | 717 | value = optionsValue; |
michael@0 | 718 | supportedExtensionAddition = ""; |
michael@0 | 719 | } |
michael@0 | 720 | } |
michael@0 | 721 | |
michael@0 | 722 | // Steps 11.i-k. |
michael@0 | 723 | result[key] = value; |
michael@0 | 724 | supportedExtension += supportedExtensionAddition; |
michael@0 | 725 | i++; |
michael@0 | 726 | } |
michael@0 | 727 | |
michael@0 | 728 | // Step 12. |
michael@0 | 729 | if (supportedExtension.length > 2) { |
michael@0 | 730 | var preExtension = callFunction(std_String_substring, foundLocale, 0, extensionIndex); |
michael@0 | 731 | var postExtension = callFunction(std_String_substring, foundLocale, extensionIndex); |
michael@0 | 732 | foundLocale = preExtension + supportedExtension + postExtension; |
michael@0 | 733 | } |
michael@0 | 734 | |
michael@0 | 735 | // Steps 13-14. |
michael@0 | 736 | result.locale = foundLocale; |
michael@0 | 737 | return result; |
michael@0 | 738 | } |
michael@0 | 739 | |
michael@0 | 740 | |
michael@0 | 741 | /** |
michael@0 | 742 | * Returns the subset of requestedLocales for which availableLocales has a |
michael@0 | 743 | * matching (possibly fallback) locale. Locales appear in the same order in the |
michael@0 | 744 | * returned list as in the input list. |
michael@0 | 745 | * |
michael@0 | 746 | * Spec: ECMAScript Internationalization API Specification, 9.2.6. |
michael@0 | 747 | */ |
michael@0 | 748 | function LookupSupportedLocales(availableLocales, requestedLocales) { |
michael@0 | 749 | // Steps 1-2. |
michael@0 | 750 | var len = requestedLocales.length; |
michael@0 | 751 | var subset = new List(); |
michael@0 | 752 | |
michael@0 | 753 | // Steps 3-4. |
michael@0 | 754 | var k = 0; |
michael@0 | 755 | while (k < len) { |
michael@0 | 756 | // Steps 4.a-b. |
michael@0 | 757 | var locale = requestedLocales[k]; |
michael@0 | 758 | var noExtensionsLocale = removeUnicodeExtensions(locale); |
michael@0 | 759 | |
michael@0 | 760 | // Step 4.c-d. |
michael@0 | 761 | var availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale); |
michael@0 | 762 | if (availableLocale !== undefined) |
michael@0 | 763 | subset.push(locale); |
michael@0 | 764 | |
michael@0 | 765 | // Step 4.e. |
michael@0 | 766 | k++; |
michael@0 | 767 | } |
michael@0 | 768 | |
michael@0 | 769 | // Steps 5-6. |
michael@0 | 770 | return subset.slice(0); |
michael@0 | 771 | } |
michael@0 | 772 | |
michael@0 | 773 | |
michael@0 | 774 | /** |
michael@0 | 775 | * Returns the subset of requestedLocales for which availableLocales has a |
michael@0 | 776 | * matching (possibly fallback) locale. Locales appear in the same order in the |
michael@0 | 777 | * returned list as in the input list. |
michael@0 | 778 | * |
michael@0 | 779 | * Spec: ECMAScript Internationalization API Specification, 9.2.7. |
michael@0 | 780 | */ |
michael@0 | 781 | function BestFitSupportedLocales(availableLocales, requestedLocales) { |
michael@0 | 782 | // don't have anything better |
michael@0 | 783 | return LookupSupportedLocales(availableLocales, requestedLocales); |
michael@0 | 784 | } |
michael@0 | 785 | |
michael@0 | 786 | |
michael@0 | 787 | /** |
michael@0 | 788 | * Returns the subset of requestedLocales for which availableLocales has a |
michael@0 | 789 | * matching (possibly fallback) locale. Locales appear in the same order in the |
michael@0 | 790 | * returned list as in the input list. |
michael@0 | 791 | * |
michael@0 | 792 | * Spec: ECMAScript Internationalization API Specification, 9.2.8. |
michael@0 | 793 | */ |
michael@0 | 794 | function SupportedLocales(availableLocales, requestedLocales, options) { |
michael@0 | 795 | /*jshint laxbreak: true */ |
michael@0 | 796 | |
michael@0 | 797 | // Step 1. |
michael@0 | 798 | var matcher; |
michael@0 | 799 | if (options !== undefined) { |
michael@0 | 800 | // Steps 1.a-b. |
michael@0 | 801 | options = ToObject(options); |
michael@0 | 802 | matcher = options.localeMatcher; |
michael@0 | 803 | |
michael@0 | 804 | // Step 1.c. |
michael@0 | 805 | if (matcher !== undefined) { |
michael@0 | 806 | matcher = ToString(matcher); |
michael@0 | 807 | if (matcher !== "lookup" && matcher !== "best fit") |
michael@0 | 808 | ThrowError(JSMSG_INVALID_LOCALE_MATCHER, matcher); |
michael@0 | 809 | } |
michael@0 | 810 | } |
michael@0 | 811 | |
michael@0 | 812 | // Steps 2-3. |
michael@0 | 813 | var subset = (matcher === undefined || matcher === "best fit") |
michael@0 | 814 | ? BestFitSupportedLocales(availableLocales, requestedLocales) |
michael@0 | 815 | : LookupSupportedLocales(availableLocales, requestedLocales); |
michael@0 | 816 | |
michael@0 | 817 | // Step 4. |
michael@0 | 818 | for (var i = 0; i < subset.length; i++) { |
michael@0 | 819 | _DefineValueProperty(subset, i, subset[i], |
michael@0 | 820 | ATTR_ENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE); |
michael@0 | 821 | } |
michael@0 | 822 | _DefineValueProperty(subset, "length", subset.length, |
michael@0 | 823 | ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE); |
michael@0 | 824 | |
michael@0 | 825 | // Step 5. |
michael@0 | 826 | return subset; |
michael@0 | 827 | } |
michael@0 | 828 | |
michael@0 | 829 | |
michael@0 | 830 | /** |
michael@0 | 831 | * Extracts a property value from the provided options object, converts it to |
michael@0 | 832 | * the required type, checks whether it is one of a list of allowed values, |
michael@0 | 833 | * and fills in a fallback value if necessary. |
michael@0 | 834 | * |
michael@0 | 835 | * Spec: ECMAScript Internationalization API Specification, 9.2.9. |
michael@0 | 836 | */ |
michael@0 | 837 | function GetOption(options, property, type, values, fallback) { |
michael@0 | 838 | // Step 1. |
michael@0 | 839 | var value = options[property]; |
michael@0 | 840 | |
michael@0 | 841 | // Step 2. |
michael@0 | 842 | if (value !== undefined) { |
michael@0 | 843 | // Steps 2.a-c. |
michael@0 | 844 | if (type === "boolean") |
michael@0 | 845 | value = ToBoolean(value); |
michael@0 | 846 | else if (type === "string") |
michael@0 | 847 | value = ToString(value); |
michael@0 | 848 | else |
michael@0 | 849 | assert(false, "GetOption"); |
michael@0 | 850 | |
michael@0 | 851 | // Step 2.d. |
michael@0 | 852 | if (values !== undefined && callFunction(std_Array_indexOf, values, value) === -1) |
michael@0 | 853 | ThrowError(JSMSG_INVALID_OPTION_VALUE, property, value); |
michael@0 | 854 | |
michael@0 | 855 | // Step 2.e. |
michael@0 | 856 | return value; |
michael@0 | 857 | } |
michael@0 | 858 | |
michael@0 | 859 | // Step 3. |
michael@0 | 860 | return fallback; |
michael@0 | 861 | } |
michael@0 | 862 | |
michael@0 | 863 | /** |
michael@0 | 864 | * Extracts a property value from the provided options object, converts it to a |
michael@0 | 865 | * Number value, checks whether it is in the allowed range, and fills in a |
michael@0 | 866 | * fallback value if necessary. |
michael@0 | 867 | * |
michael@0 | 868 | * Spec: ECMAScript Internationalization API Specification, 9.2.10. |
michael@0 | 869 | */ |
michael@0 | 870 | function GetNumberOption(options, property, minimum, maximum, fallback) { |
michael@0 | 871 | assert(typeof minimum === "number", "GetNumberOption"); |
michael@0 | 872 | assert(typeof maximum === "number", "GetNumberOption"); |
michael@0 | 873 | assert(fallback === undefined || (fallback >= minimum && fallback <= maximum), "GetNumberOption"); |
michael@0 | 874 | |
michael@0 | 875 | // Step 1. |
michael@0 | 876 | var value = options[property]; |
michael@0 | 877 | |
michael@0 | 878 | // Step 2. |
michael@0 | 879 | if (value !== undefined) { |
michael@0 | 880 | value = ToNumber(value); |
michael@0 | 881 | if (std_isNaN(value) || value < minimum || value > maximum) |
michael@0 | 882 | ThrowError(JSMSG_INVALID_DIGITS_VALUE, value); |
michael@0 | 883 | return std_Math_floor(value); |
michael@0 | 884 | } |
michael@0 | 885 | |
michael@0 | 886 | // Step 3. |
michael@0 | 887 | return fallback; |
michael@0 | 888 | } |
michael@0 | 889 | |
michael@0 | 890 | |
michael@0 | 891 | /********** Property access for Intl objects **********/ |
michael@0 | 892 | |
michael@0 | 893 | |
michael@0 | 894 | /** |
michael@0 | 895 | * Set a normal public property p of o to value v, but use Object.defineProperty |
michael@0 | 896 | * to avoid interference from setters on Object.prototype. |
michael@0 | 897 | */ |
michael@0 | 898 | function defineProperty(o, p, v) { |
michael@0 | 899 | _DefineValueProperty(o, p, v, ATTR_ENUMERABLE | ATTR_CONFIGURABLE | ATTR_WRITABLE); |
michael@0 | 900 | } |
michael@0 | 901 | |
michael@0 | 902 | |
michael@0 | 903 | /** |
michael@0 | 904 | * Weak map used to track the initialize-as-Intl status (and, if an object has |
michael@0 | 905 | * been so initialized, the Intl-specific internal properties) of all objects. |
michael@0 | 906 | * Presence of an object as a key within this map indicates that the object has |
michael@0 | 907 | * its [[initializedIntlObject]] internal property set to true. The associated |
michael@0 | 908 | * value is an object whose structure is documented in |initializeIntlObject| |
michael@0 | 909 | * below. |
michael@0 | 910 | * |
michael@0 | 911 | * Ideally we'd be using private symbols for internal properties, but |
michael@0 | 912 | * SpiderMonkey doesn't have those yet. |
michael@0 | 913 | */ |
michael@0 | 914 | var internalsMap = new WeakMap(); |
michael@0 | 915 | |
michael@0 | 916 | |
michael@0 | 917 | /** |
michael@0 | 918 | * Set the [[initializedIntlObject]] internal property of |obj| to true. |
michael@0 | 919 | */ |
michael@0 | 920 | function initializeIntlObject(obj) { |
michael@0 | 921 | assert(IsObject(obj), "Non-object passed to initializeIntlObject"); |
michael@0 | 922 | |
michael@0 | 923 | // Intl-initialized objects are weird. They have [[initializedIntlObject]] |
michael@0 | 924 | // set on them, but they don't *necessarily* have any other properties. |
michael@0 | 925 | |
michael@0 | 926 | var internals = std_Object_create(null); |
michael@0 | 927 | |
michael@0 | 928 | // The meaning of an internals object for an object |obj| is as follows. |
michael@0 | 929 | // |
michael@0 | 930 | // If the .type is "partial", |obj| has [[initializedIntlObject]] set but |
michael@0 | 931 | // nothing else. No other property of |internals| can be used. (This |
michael@0 | 932 | // occurs when InitializeCollator or similar marks an object as |
michael@0 | 933 | // [[initializedIntlObject]] but fails before marking it as the appropriate |
michael@0 | 934 | // more-specific type ["Collator", "DateTimeFormat", "NumberFormat"].) |
michael@0 | 935 | // |
michael@0 | 936 | // Otherwise, the .type indicates the type of Intl object that |obj| is: |
michael@0 | 937 | // "Collator", "DateTimeFormat", or "NumberFormat" (likely with more coming |
michael@0 | 938 | // in future Intl specs). In these cases |obj| *conceptually* also has |
michael@0 | 939 | // [[initializedCollator]] or similar set, and all the other properties |
michael@0 | 940 | // implied by that. |
michael@0 | 941 | // |
michael@0 | 942 | // If |internals| doesn't have a "partial" .type, two additional properties |
michael@0 | 943 | // have meaning. The .lazyData property stores information needed to |
michael@0 | 944 | // compute -- without observable side effects -- the actual internal Intl |
michael@0 | 945 | // properties of |obj|. If it is non-null, then the actual internal |
michael@0 | 946 | // properties haven't been computed, and .lazyData must be processed by |
michael@0 | 947 | // |setInternalProperties| before internal Intl property values are |
michael@0 | 948 | // available. If it is null, then the .internalProps property contains an |
michael@0 | 949 | // object whose properties are the internal Intl properties of |obj|. |
michael@0 | 950 | |
michael@0 | 951 | internals.type = "partial"; |
michael@0 | 952 | internals.lazyData = null; |
michael@0 | 953 | internals.internalProps = null; |
michael@0 | 954 | |
michael@0 | 955 | callFunction(std_WeakMap_set, internalsMap, obj, internals); |
michael@0 | 956 | return internals; |
michael@0 | 957 | } |
michael@0 | 958 | |
michael@0 | 959 | |
michael@0 | 960 | /** |
michael@0 | 961 | * Mark |internals| as having the given type and lazy data. |
michael@0 | 962 | */ |
michael@0 | 963 | function setLazyData(internals, type, lazyData) |
michael@0 | 964 | { |
michael@0 | 965 | assert(internals.type === "partial", "can't set lazy data for anything but a newborn"); |
michael@0 | 966 | assert(type === "Collator" || type === "DateTimeFormat" || type == "NumberFormat", "bad type"); |
michael@0 | 967 | assert(IsObject(lazyData), "non-object lazy data"); |
michael@0 | 968 | |
michael@0 | 969 | // Set in reverse order so that the .type change is a barrier. |
michael@0 | 970 | internals.lazyData = lazyData; |
michael@0 | 971 | internals.type = type; |
michael@0 | 972 | } |
michael@0 | 973 | |
michael@0 | 974 | |
michael@0 | 975 | /** |
michael@0 | 976 | * Set the internal properties object for an |internals| object previously |
michael@0 | 977 | * associated with lazy data. |
michael@0 | 978 | */ |
michael@0 | 979 | function setInternalProperties(internals, internalProps) |
michael@0 | 980 | { |
michael@0 | 981 | assert(internals.type !== "partial", "newborn internals can't have computed internals"); |
michael@0 | 982 | assert(IsObject(internals.lazyData), "lazy data must exist already"); |
michael@0 | 983 | assert(IsObject(internalProps), "internalProps argument should be an object"); |
michael@0 | 984 | |
michael@0 | 985 | // Set in reverse order so that the .lazyData nulling is a barrier. |
michael@0 | 986 | internals.internalProps = internalProps; |
michael@0 | 987 | internals.lazyData = null; |
michael@0 | 988 | } |
michael@0 | 989 | |
michael@0 | 990 | |
michael@0 | 991 | /** |
michael@0 | 992 | * Get the existing internal properties out of a non-newborn |internals|, or |
michael@0 | 993 | * null if none have been computed. |
michael@0 | 994 | */ |
michael@0 | 995 | function maybeInternalProperties(internals) |
michael@0 | 996 | { |
michael@0 | 997 | assert(IsObject(internals), "non-object passed to maybeInternalProperties"); |
michael@0 | 998 | assert(internals.type !== "partial", "maybeInternalProperties must only be used on completely-initialized internals objects"); |
michael@0 | 999 | var lazyData = internals.lazyData; |
michael@0 | 1000 | if (lazyData) |
michael@0 | 1001 | return null; |
michael@0 | 1002 | assert(IsObject(internals.internalProps), "missing lazy data and computed internals"); |
michael@0 | 1003 | return internals.internalProps; |
michael@0 | 1004 | } |
michael@0 | 1005 | |
michael@0 | 1006 | |
michael@0 | 1007 | /** |
michael@0 | 1008 | * Return whether |obj| has an[[initializedIntlObject]] property set to true. |
michael@0 | 1009 | */ |
michael@0 | 1010 | function isInitializedIntlObject(obj) { |
michael@0 | 1011 | #ifdef DEBUG |
michael@0 | 1012 | var internals = callFunction(std_WeakMap_get, internalsMap, obj); |
michael@0 | 1013 | if (IsObject(internals)) { |
michael@0 | 1014 | assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type"); |
michael@0 | 1015 | var type = internals.type; |
michael@0 | 1016 | assert(type === "partial" || type === "Collator" || type === "DateTimeFormat" || type === "NumberFormat", "unexpected type"); |
michael@0 | 1017 | assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData"); |
michael@0 | 1018 | assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps"); |
michael@0 | 1019 | } else { |
michael@0 | 1020 | assert(internals === undefined, "bad mapping for |obj|"); |
michael@0 | 1021 | } |
michael@0 | 1022 | #endif |
michael@0 | 1023 | return callFunction(std_WeakMap_has, internalsMap, obj); |
michael@0 | 1024 | } |
michael@0 | 1025 | |
michael@0 | 1026 | |
michael@0 | 1027 | /** |
michael@0 | 1028 | * Check that |obj| meets the requirements for "this Collator object", "this |
michael@0 | 1029 | * NumberFormat object", or "this DateTimeFormat object" as used in the method |
michael@0 | 1030 | * with the given name. Throw a TypeError if |obj| doesn't meet these |
michael@0 | 1031 | * requirements. But if it does, return |obj|'s internals object (*not* the |
michael@0 | 1032 | * object holding its internal properties!), associated with it by |
michael@0 | 1033 | * |internalsMap|, with structure specified above. |
michael@0 | 1034 | * |
michael@0 | 1035 | * Spec: ECMAScript Internationalization API Specification, 10.3. |
michael@0 | 1036 | * Spec: ECMAScript Internationalization API Specification, 11.3. |
michael@0 | 1037 | * Spec: ECMAScript Internationalization API Specification, 12.3. |
michael@0 | 1038 | */ |
michael@0 | 1039 | function getIntlObjectInternals(obj, className, methodName) { |
michael@0 | 1040 | assert(typeof className === "string", "bad className for getIntlObjectInternals"); |
michael@0 | 1041 | |
michael@0 | 1042 | var internals = callFunction(std_WeakMap_get, internalsMap, obj); |
michael@0 | 1043 | assert(internals === undefined || isInitializedIntlObject(obj), "bad mapping in internalsMap"); |
michael@0 | 1044 | |
michael@0 | 1045 | if (internals === undefined || internals.type !== className) |
michael@0 | 1046 | ThrowError(JSMSG_INTL_OBJECT_NOT_INITED, className, methodName, className); |
michael@0 | 1047 | |
michael@0 | 1048 | return internals; |
michael@0 | 1049 | } |
michael@0 | 1050 | |
michael@0 | 1051 | |
michael@0 | 1052 | /** |
michael@0 | 1053 | * Get the internal properties of known-Intl object |obj|. For use only by |
michael@0 | 1054 | * C++ code that knows what it's doing! |
michael@0 | 1055 | */ |
michael@0 | 1056 | function getInternals(obj) |
michael@0 | 1057 | { |
michael@0 | 1058 | assert(isInitializedIntlObject(obj), "for use only on guaranteed Intl objects"); |
michael@0 | 1059 | |
michael@0 | 1060 | var internals = callFunction(std_WeakMap_get, internalsMap, obj); |
michael@0 | 1061 | |
michael@0 | 1062 | assert(internals.type !== "partial", "must have been successfully initialized"); |
michael@0 | 1063 | var lazyData = internals.lazyData; |
michael@0 | 1064 | if (!lazyData) |
michael@0 | 1065 | return internals.internalProps; |
michael@0 | 1066 | |
michael@0 | 1067 | var internalProps; |
michael@0 | 1068 | var type = internals.type; |
michael@0 | 1069 | if (type === "Collator") |
michael@0 | 1070 | internalProps = resolveCollatorInternals(lazyData) |
michael@0 | 1071 | else if (type === "DateTimeFormat") |
michael@0 | 1072 | internalProps = resolveDateTimeFormatInternals(lazyData) |
michael@0 | 1073 | else |
michael@0 | 1074 | internalProps = resolveNumberFormatInternals(lazyData); |
michael@0 | 1075 | setInternalProperties(internals, internalProps); |
michael@0 | 1076 | return internalProps; |
michael@0 | 1077 | } |
michael@0 | 1078 | |
michael@0 | 1079 | |
michael@0 | 1080 | /********** Intl.Collator **********/ |
michael@0 | 1081 | |
michael@0 | 1082 | |
michael@0 | 1083 | /** |
michael@0 | 1084 | * Mapping from Unicode extension keys for collation to options properties, |
michael@0 | 1085 | * their types and permissible values. |
michael@0 | 1086 | * |
michael@0 | 1087 | * Spec: ECMAScript Internationalization API Specification, 10.1.1. |
michael@0 | 1088 | */ |
michael@0 | 1089 | var collatorKeyMappings = { |
michael@0 | 1090 | kn: {property: "numeric", type: "boolean"}, |
michael@0 | 1091 | kf: {property: "caseFirst", type: "string", values: ["upper", "lower", "false"]} |
michael@0 | 1092 | }; |
michael@0 | 1093 | |
michael@0 | 1094 | |
michael@0 | 1095 | /** |
michael@0 | 1096 | * Compute an internal properties object from |lazyCollatorData|. |
michael@0 | 1097 | */ |
michael@0 | 1098 | function resolveCollatorInternals(lazyCollatorData) |
michael@0 | 1099 | { |
michael@0 | 1100 | assert(IsObject(lazyCollatorData), "lazy data not an object?"); |
michael@0 | 1101 | |
michael@0 | 1102 | var internalProps = std_Object_create(null); |
michael@0 | 1103 | |
michael@0 | 1104 | // Step 7. |
michael@0 | 1105 | internalProps.usage = lazyCollatorData.usage; |
michael@0 | 1106 | |
michael@0 | 1107 | // Step 8. |
michael@0 | 1108 | var Collator = collatorInternalProperties; |
michael@0 | 1109 | |
michael@0 | 1110 | // Step 9. |
michael@0 | 1111 | var collatorIsSorting = lazyCollatorData.usage === "sort"; |
michael@0 | 1112 | var localeData = collatorIsSorting |
michael@0 | 1113 | ? Collator.sortLocaleData |
michael@0 | 1114 | : Collator.searchLocaleData; |
michael@0 | 1115 | |
michael@0 | 1116 | // Compute effective locale. |
michael@0 | 1117 | // Step 14. |
michael@0 | 1118 | var relevantExtensionKeys = Collator.relevantExtensionKeys; |
michael@0 | 1119 | |
michael@0 | 1120 | // Step 15. |
michael@0 | 1121 | var r = ResolveLocale(Collator.availableLocales(), |
michael@0 | 1122 | lazyCollatorData.requestedLocales, |
michael@0 | 1123 | lazyCollatorData.opt, |
michael@0 | 1124 | relevantExtensionKeys, |
michael@0 | 1125 | localeData); |
michael@0 | 1126 | |
michael@0 | 1127 | // Step 16. |
michael@0 | 1128 | internalProps.locale = r.locale; |
michael@0 | 1129 | |
michael@0 | 1130 | // Steps 17-19. |
michael@0 | 1131 | var key, property, value, mapping; |
michael@0 | 1132 | var i = 0, len = relevantExtensionKeys.length; |
michael@0 | 1133 | while (i < len) { |
michael@0 | 1134 | // Step 19.a. |
michael@0 | 1135 | key = relevantExtensionKeys[i]; |
michael@0 | 1136 | if (key === "co") { |
michael@0 | 1137 | // Step 19.b. |
michael@0 | 1138 | property = "collation"; |
michael@0 | 1139 | value = r.co === null ? "default" : r.co; |
michael@0 | 1140 | } else { |
michael@0 | 1141 | // Step 19.c. |
michael@0 | 1142 | mapping = collatorKeyMappings[key]; |
michael@0 | 1143 | property = mapping.property; |
michael@0 | 1144 | value = r[key]; |
michael@0 | 1145 | if (mapping.type === "boolean") |
michael@0 | 1146 | value = value === "true"; |
michael@0 | 1147 | } |
michael@0 | 1148 | |
michael@0 | 1149 | // Step 19.d. |
michael@0 | 1150 | internalProps[property] = value; |
michael@0 | 1151 | |
michael@0 | 1152 | // Step 19.e. |
michael@0 | 1153 | i++; |
michael@0 | 1154 | } |
michael@0 | 1155 | |
michael@0 | 1156 | // Compute remaining collation options. |
michael@0 | 1157 | // Steps 21-22. |
michael@0 | 1158 | var s = lazyCollatorData.rawSensitivity; |
michael@0 | 1159 | if (s === undefined) { |
michael@0 | 1160 | if (collatorIsSorting) { |
michael@0 | 1161 | // Step 21.a. |
michael@0 | 1162 | s = "variant"; |
michael@0 | 1163 | } else { |
michael@0 | 1164 | // Step 21.b. |
michael@0 | 1165 | var dataLocale = r.dataLocale; |
michael@0 | 1166 | var dataLocaleData = localeData(dataLocale); |
michael@0 | 1167 | s = dataLocaleData.sensitivity; |
michael@0 | 1168 | } |
michael@0 | 1169 | } |
michael@0 | 1170 | internalProps.sensitivity = s; |
michael@0 | 1171 | |
michael@0 | 1172 | // Step 24. |
michael@0 | 1173 | internalProps.ignorePunctuation = lazyCollatorData.ignorePunctuation; |
michael@0 | 1174 | |
michael@0 | 1175 | // Step 25. |
michael@0 | 1176 | internalProps.boundFormat = undefined; |
michael@0 | 1177 | |
michael@0 | 1178 | // The caller is responsible for associating |internalProps| with the right |
michael@0 | 1179 | // object using |setInternalProperties|. |
michael@0 | 1180 | return internalProps; |
michael@0 | 1181 | } |
michael@0 | 1182 | |
michael@0 | 1183 | |
michael@0 | 1184 | /** |
michael@0 | 1185 | * Returns an object containing the Collator internal properties of |obj|, or |
michael@0 | 1186 | * throws a TypeError if |obj| isn't Collator-initialized. |
michael@0 | 1187 | */ |
michael@0 | 1188 | function getCollatorInternals(obj, methodName) { |
michael@0 | 1189 | var internals = getIntlObjectInternals(obj, "Collator", methodName); |
michael@0 | 1190 | assert(internals.type === "Collator", "bad type escaped getIntlObjectInternals"); |
michael@0 | 1191 | |
michael@0 | 1192 | // If internal properties have already been computed, use them. |
michael@0 | 1193 | var internalProps = maybeInternalProperties(internals); |
michael@0 | 1194 | if (internalProps) |
michael@0 | 1195 | return internalProps; |
michael@0 | 1196 | |
michael@0 | 1197 | // Otherwise it's time to fully create them. |
michael@0 | 1198 | internalProps = resolveCollatorInternals(internals.lazyData); |
michael@0 | 1199 | setInternalProperties(internals, internalProps); |
michael@0 | 1200 | return internalProps; |
michael@0 | 1201 | } |
michael@0 | 1202 | |
michael@0 | 1203 | |
michael@0 | 1204 | /** |
michael@0 | 1205 | * Initializes an object as a Collator. |
michael@0 | 1206 | * |
michael@0 | 1207 | * This method is complicated a moderate bit by its implementing initialization |
michael@0 | 1208 | * as a *lazy* concept. Everything that must happen now, does -- but we defer |
michael@0 | 1209 | * all the work we can until the object is actually used as a Collator. This |
michael@0 | 1210 | * later work occurs in |resolveCollatorInternals|; steps not noted here occur |
michael@0 | 1211 | * there. |
michael@0 | 1212 | * |
michael@0 | 1213 | * Spec: ECMAScript Internationalization API Specification, 10.1.1. |
michael@0 | 1214 | */ |
michael@0 | 1215 | function InitializeCollator(collator, locales, options) { |
michael@0 | 1216 | assert(IsObject(collator), "InitializeCollator"); |
michael@0 | 1217 | |
michael@0 | 1218 | // Step 1. |
michael@0 | 1219 | if (isInitializedIntlObject(collator)) |
michael@0 | 1220 | ThrowError(JSMSG_INTL_OBJECT_REINITED); |
michael@0 | 1221 | |
michael@0 | 1222 | // Step 2. |
michael@0 | 1223 | var internals = initializeIntlObject(collator); |
michael@0 | 1224 | |
michael@0 | 1225 | // Lazy Collator data has the following structure: |
michael@0 | 1226 | // |
michael@0 | 1227 | // { |
michael@0 | 1228 | // requestedLocales: List of locales, |
michael@0 | 1229 | // usage: "sort" / "search", |
michael@0 | 1230 | // opt: // opt object computed in InitializeCollator |
michael@0 | 1231 | // { |
michael@0 | 1232 | // localeMatcher: "lookup" / "best fit", |
michael@0 | 1233 | // kn: true / false / undefined, |
michael@0 | 1234 | // kf: "upper" / "lower" / "false" / undefined |
michael@0 | 1235 | // } |
michael@0 | 1236 | // rawSensitivity: "base" / "accent" / "case" / "variant" / undefined, |
michael@0 | 1237 | // ignorePunctuation: true / false |
michael@0 | 1238 | // } |
michael@0 | 1239 | // |
michael@0 | 1240 | // Note that lazy data is only installed as a final step of initialization, |
michael@0 | 1241 | // so every Collator lazy data object has *all* these properties, never a |
michael@0 | 1242 | // subset of them. |
michael@0 | 1243 | var lazyCollatorData = std_Object_create(null); |
michael@0 | 1244 | |
michael@0 | 1245 | // Step 3. |
michael@0 | 1246 | var requestedLocales = CanonicalizeLocaleList(locales); |
michael@0 | 1247 | lazyCollatorData.requestedLocales = requestedLocales; |
michael@0 | 1248 | |
michael@0 | 1249 | // Steps 4-5. |
michael@0 | 1250 | // |
michael@0 | 1251 | // If we ever need more speed here at startup, we should try to detect the |
michael@0 | 1252 | // case where |options === undefined| and Object.prototype hasn't been |
michael@0 | 1253 | // mucked with. (|options| is fully consumed in this method, so it's not a |
michael@0 | 1254 | // concern that Object.prototype might be touched between now and when |
michael@0 | 1255 | // |resolveCollatorInternals| is called.) For now, just keep it simple. |
michael@0 | 1256 | if (options === undefined) |
michael@0 | 1257 | options = {}; |
michael@0 | 1258 | else |
michael@0 | 1259 | options = ToObject(options); |
michael@0 | 1260 | |
michael@0 | 1261 | // Compute options that impact interpretation of locale. |
michael@0 | 1262 | // Step 6. |
michael@0 | 1263 | var u = GetOption(options, "usage", "string", ["sort", "search"], "sort"); |
michael@0 | 1264 | lazyCollatorData.usage = u; |
michael@0 | 1265 | |
michael@0 | 1266 | // Step 10. |
michael@0 | 1267 | var opt = new Record(); |
michael@0 | 1268 | lazyCollatorData.opt = opt; |
michael@0 | 1269 | |
michael@0 | 1270 | // Steps 11-12. |
michael@0 | 1271 | var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit"); |
michael@0 | 1272 | opt.localeMatcher = matcher; |
michael@0 | 1273 | |
michael@0 | 1274 | // Step 13, unrolled. |
michael@0 | 1275 | var numericValue = GetOption(options, "numeric", "boolean", undefined, undefined); |
michael@0 | 1276 | if (numericValue !== undefined) |
michael@0 | 1277 | numericValue = callFunction(std_Boolean_toString, numericValue); |
michael@0 | 1278 | opt.kn = numericValue; |
michael@0 | 1279 | |
michael@0 | 1280 | var caseFirstValue = GetOption(options, "caseFirst", "string", ["upper", "lower", "false"], undefined); |
michael@0 | 1281 | opt.kf = caseFirstValue; |
michael@0 | 1282 | |
michael@0 | 1283 | // Compute remaining collation options. |
michael@0 | 1284 | // Step 20. |
michael@0 | 1285 | var s = GetOption(options, "sensitivity", "string", |
michael@0 | 1286 | ["base", "accent", "case", "variant"], undefined); |
michael@0 | 1287 | lazyCollatorData.rawSensitivity = s; |
michael@0 | 1288 | |
michael@0 | 1289 | // Step 23. |
michael@0 | 1290 | var ip = GetOption(options, "ignorePunctuation", "boolean", undefined, false); |
michael@0 | 1291 | lazyCollatorData.ignorePunctuation = ip; |
michael@0 | 1292 | |
michael@0 | 1293 | // Step 26. |
michael@0 | 1294 | // |
michael@0 | 1295 | // We've done everything that must be done now: mark the lazy data as fully |
michael@0 | 1296 | // computed and install it. |
michael@0 | 1297 | setLazyData(internals, "Collator", lazyCollatorData); |
michael@0 | 1298 | } |
michael@0 | 1299 | |
michael@0 | 1300 | |
michael@0 | 1301 | /** |
michael@0 | 1302 | * Returns the subset of the given locale list for which this locale list has a |
michael@0 | 1303 | * matching (possibly fallback) locale. Locales appear in the same order in the |
michael@0 | 1304 | * returned list as in the input list. |
michael@0 | 1305 | * |
michael@0 | 1306 | * Spec: ECMAScript Internationalization API Specification, 10.2.2. |
michael@0 | 1307 | */ |
michael@0 | 1308 | function Intl_Collator_supportedLocalesOf(locales /*, options*/) { |
michael@0 | 1309 | var options = arguments.length > 1 ? arguments[1] : undefined; |
michael@0 | 1310 | |
michael@0 | 1311 | var availableLocales = collatorInternalProperties.availableLocales(); |
michael@0 | 1312 | var requestedLocales = CanonicalizeLocaleList(locales); |
michael@0 | 1313 | return SupportedLocales(availableLocales, requestedLocales, options); |
michael@0 | 1314 | } |
michael@0 | 1315 | |
michael@0 | 1316 | |
michael@0 | 1317 | /** |
michael@0 | 1318 | * Collator internal properties. |
michael@0 | 1319 | * |
michael@0 | 1320 | * Spec: ECMAScript Internationalization API Specification, 9.1 and 10.2.3. |
michael@0 | 1321 | */ |
michael@0 | 1322 | var collatorInternalProperties = { |
michael@0 | 1323 | sortLocaleData: collatorSortLocaleData, |
michael@0 | 1324 | searchLocaleData: collatorSearchLocaleData, |
michael@0 | 1325 | _availableLocales: null, |
michael@0 | 1326 | availableLocales: function() |
michael@0 | 1327 | { |
michael@0 | 1328 | var locales = this._availableLocales; |
michael@0 | 1329 | if (locales) |
michael@0 | 1330 | return locales; |
michael@0 | 1331 | return (this._availableLocales = |
michael@0 | 1332 | addOldStyleLanguageTags(intl_Collator_availableLocales())); |
michael@0 | 1333 | }, |
michael@0 | 1334 | relevantExtensionKeys: ["co", "kn"] |
michael@0 | 1335 | }; |
michael@0 | 1336 | |
michael@0 | 1337 | |
michael@0 | 1338 | function collatorSortLocaleData(locale) { |
michael@0 | 1339 | var collations = intl_availableCollations(locale); |
michael@0 | 1340 | callFunction(std_Array_unshift, collations, null); |
michael@0 | 1341 | return { |
michael@0 | 1342 | co: collations, |
michael@0 | 1343 | kn: ["false", "true"] |
michael@0 | 1344 | }; |
michael@0 | 1345 | } |
michael@0 | 1346 | |
michael@0 | 1347 | |
michael@0 | 1348 | function collatorSearchLocaleData(locale) { |
michael@0 | 1349 | return { |
michael@0 | 1350 | co: [null], |
michael@0 | 1351 | kn: ["false", "true"], |
michael@0 | 1352 | // In theory the default sensitivity is locale dependent; |
michael@0 | 1353 | // in reality the CLDR/ICU default strength is always tertiary. |
michael@0 | 1354 | sensitivity: "variant" |
michael@0 | 1355 | }; |
michael@0 | 1356 | } |
michael@0 | 1357 | |
michael@0 | 1358 | |
michael@0 | 1359 | /** |
michael@0 | 1360 | * Function to be bound and returned by Intl.Collator.prototype.format. |
michael@0 | 1361 | * |
michael@0 | 1362 | * Spec: ECMAScript Internationalization API Specification, 12.3.2. |
michael@0 | 1363 | */ |
michael@0 | 1364 | function collatorCompareToBind(x, y) { |
michael@0 | 1365 | // Steps 1.a.i-ii implemented by ECMAScript declaration binding instantiation, |
michael@0 | 1366 | // ES5.1 10.5, step 4.d.ii. |
michael@0 | 1367 | |
michael@0 | 1368 | // Step 1.a.iii-v. |
michael@0 | 1369 | var X = ToString(x); |
michael@0 | 1370 | var Y = ToString(y); |
michael@0 | 1371 | return intl_CompareStrings(this, X, Y); |
michael@0 | 1372 | } |
michael@0 | 1373 | |
michael@0 | 1374 | |
michael@0 | 1375 | /** |
michael@0 | 1376 | * Returns a function bound to this Collator that compares x (converted to a |
michael@0 | 1377 | * String value) and y (converted to a String value), |
michael@0 | 1378 | * and returns a number less than 0 if x < y, 0 if x = y, or a number greater |
michael@0 | 1379 | * than 0 if x > y according to the sort order for the locale and collation |
michael@0 | 1380 | * options of this Collator object. |
michael@0 | 1381 | * |
michael@0 | 1382 | * Spec: ECMAScript Internationalization API Specification, 10.3.2. |
michael@0 | 1383 | */ |
michael@0 | 1384 | function Intl_Collator_compare_get() { |
michael@0 | 1385 | // Check "this Collator object" per introduction of section 10.3. |
michael@0 | 1386 | var internals = getCollatorInternals(this, "compare"); |
michael@0 | 1387 | |
michael@0 | 1388 | // Step 1. |
michael@0 | 1389 | if (internals.boundCompare === undefined) { |
michael@0 | 1390 | // Step 1.a. |
michael@0 | 1391 | var F = collatorCompareToBind; |
michael@0 | 1392 | |
michael@0 | 1393 | // Step 1.b-d. |
michael@0 | 1394 | var bc = callFunction(std_Function_bind, F, this); |
michael@0 | 1395 | internals.boundCompare = bc; |
michael@0 | 1396 | } |
michael@0 | 1397 | |
michael@0 | 1398 | // Step 2. |
michael@0 | 1399 | return internals.boundCompare; |
michael@0 | 1400 | } |
michael@0 | 1401 | |
michael@0 | 1402 | |
michael@0 | 1403 | /** |
michael@0 | 1404 | * Returns the resolved options for a Collator object. |
michael@0 | 1405 | * |
michael@0 | 1406 | * Spec: ECMAScript Internationalization API Specification, 10.3.3 and 10.4. |
michael@0 | 1407 | */ |
michael@0 | 1408 | function Intl_Collator_resolvedOptions() { |
michael@0 | 1409 | // Check "this Collator object" per introduction of section 10.3. |
michael@0 | 1410 | var internals = getCollatorInternals(this, "resolvedOptions"); |
michael@0 | 1411 | |
michael@0 | 1412 | var result = { |
michael@0 | 1413 | locale: internals.locale, |
michael@0 | 1414 | usage: internals.usage, |
michael@0 | 1415 | sensitivity: internals.sensitivity, |
michael@0 | 1416 | ignorePunctuation: internals.ignorePunctuation |
michael@0 | 1417 | }; |
michael@0 | 1418 | |
michael@0 | 1419 | var relevantExtensionKeys = collatorInternalProperties.relevantExtensionKeys; |
michael@0 | 1420 | for (var i = 0; i < relevantExtensionKeys.length; i++) { |
michael@0 | 1421 | var key = relevantExtensionKeys[i]; |
michael@0 | 1422 | var property = (key === "co") ? "collation" : collatorKeyMappings[key].property; |
michael@0 | 1423 | defineProperty(result, property, internals[property]); |
michael@0 | 1424 | } |
michael@0 | 1425 | return result; |
michael@0 | 1426 | } |
michael@0 | 1427 | |
michael@0 | 1428 | |
michael@0 | 1429 | /********** Intl.NumberFormat **********/ |
michael@0 | 1430 | |
michael@0 | 1431 | |
michael@0 | 1432 | /** |
michael@0 | 1433 | * NumberFormat internal properties. |
michael@0 | 1434 | * |
michael@0 | 1435 | * Spec: ECMAScript Internationalization API Specification, 9.1 and 11.2.3. |
michael@0 | 1436 | */ |
michael@0 | 1437 | var numberFormatInternalProperties = { |
michael@0 | 1438 | localeData: numberFormatLocaleData, |
michael@0 | 1439 | _availableLocales: null, |
michael@0 | 1440 | availableLocales: function() |
michael@0 | 1441 | { |
michael@0 | 1442 | var locales = this._availableLocales; |
michael@0 | 1443 | if (locales) |
michael@0 | 1444 | return locales; |
michael@0 | 1445 | return (this._availableLocales = |
michael@0 | 1446 | addOldStyleLanguageTags(intl_NumberFormat_availableLocales())); |
michael@0 | 1447 | }, |
michael@0 | 1448 | relevantExtensionKeys: ["nu"] |
michael@0 | 1449 | }; |
michael@0 | 1450 | |
michael@0 | 1451 | |
michael@0 | 1452 | /** |
michael@0 | 1453 | * Compute an internal properties object from |lazyNumberFormatData|. |
michael@0 | 1454 | */ |
michael@0 | 1455 | function resolveNumberFormatInternals(lazyNumberFormatData) { |
michael@0 | 1456 | assert(IsObject(lazyNumberFormatData), "lazy data not an object?"); |
michael@0 | 1457 | |
michael@0 | 1458 | var internalProps = std_Object_create(null); |
michael@0 | 1459 | |
michael@0 | 1460 | // Step 3. |
michael@0 | 1461 | var requestedLocales = lazyNumberFormatData.requestedLocales; |
michael@0 | 1462 | |
michael@0 | 1463 | // Compute options that impact interpretation of locale. |
michael@0 | 1464 | // Step 6. |
michael@0 | 1465 | var opt = lazyNumberFormatData.opt; |
michael@0 | 1466 | |
michael@0 | 1467 | // Compute effective locale. |
michael@0 | 1468 | // Step 9. |
michael@0 | 1469 | var NumberFormat = numberFormatInternalProperties; |
michael@0 | 1470 | |
michael@0 | 1471 | // Step 10. |
michael@0 | 1472 | var localeData = NumberFormat.localeData; |
michael@0 | 1473 | |
michael@0 | 1474 | // Step 11. |
michael@0 | 1475 | var r = ResolveLocale(NumberFormat.availableLocales(), |
michael@0 | 1476 | lazyNumberFormatData.requestedLocales, |
michael@0 | 1477 | lazyNumberFormatData.opt, |
michael@0 | 1478 | NumberFormat.relevantExtensionKeys, |
michael@0 | 1479 | localeData); |
michael@0 | 1480 | |
michael@0 | 1481 | // Steps 12-13. (Step 14 is not relevant to our implementation.) |
michael@0 | 1482 | internalProps.locale = r.locale; |
michael@0 | 1483 | internalProps.numberingSystem = r.nu; |
michael@0 | 1484 | |
michael@0 | 1485 | // Compute formatting options. |
michael@0 | 1486 | // Step 16. |
michael@0 | 1487 | var s = lazyNumberFormatData.style; |
michael@0 | 1488 | internalProps.style = s; |
michael@0 | 1489 | |
michael@0 | 1490 | // Steps 20, 22. |
michael@0 | 1491 | if (s === "currency") { |
michael@0 | 1492 | internalProps.currency = lazyNumberFormatData.currency; |
michael@0 | 1493 | internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay; |
michael@0 | 1494 | } |
michael@0 | 1495 | |
michael@0 | 1496 | // Step 24. |
michael@0 | 1497 | internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits; |
michael@0 | 1498 | |
michael@0 | 1499 | // Steps 27. |
michael@0 | 1500 | internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits; |
michael@0 | 1501 | |
michael@0 | 1502 | // Step 30. |
michael@0 | 1503 | internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits; |
michael@0 | 1504 | |
michael@0 | 1505 | // Step 33. |
michael@0 | 1506 | if ("minimumSignificantDigits" in lazyNumberFormatData) { |
michael@0 | 1507 | // Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the |
michael@0 | 1508 | // actual presence (versus undefined-ness) of these properties. |
michael@0 | 1509 | assert("maximumSignificantDigits" in lazyNumberFormatData, "min/max sig digits mismatch"); |
michael@0 | 1510 | internalProps.minimumSignificantDigits = lazyNumberFormatData.minimumSignificantDigits; |
michael@0 | 1511 | internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits; |
michael@0 | 1512 | } |
michael@0 | 1513 | |
michael@0 | 1514 | // Step 35. |
michael@0 | 1515 | internalProps.useGrouping = lazyNumberFormatData.useGrouping; |
michael@0 | 1516 | |
michael@0 | 1517 | // Step 42. |
michael@0 | 1518 | internalProps.boundFormat = undefined; |
michael@0 | 1519 | |
michael@0 | 1520 | // The caller is responsible for associating |internalProps| with the right |
michael@0 | 1521 | // object using |setInternalProperties|. |
michael@0 | 1522 | return internalProps; |
michael@0 | 1523 | } |
michael@0 | 1524 | |
michael@0 | 1525 | |
michael@0 | 1526 | /** |
michael@0 | 1527 | * Returns an object containing the NumberFormat internal properties of |obj|, |
michael@0 | 1528 | * or throws a TypeError if |obj| isn't NumberFormat-initialized. |
michael@0 | 1529 | */ |
michael@0 | 1530 | function getNumberFormatInternals(obj, methodName) { |
michael@0 | 1531 | var internals = getIntlObjectInternals(obj, "NumberFormat", methodName); |
michael@0 | 1532 | assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals"); |
michael@0 | 1533 | |
michael@0 | 1534 | // If internal properties have already been computed, use them. |
michael@0 | 1535 | var internalProps = maybeInternalProperties(internals); |
michael@0 | 1536 | if (internalProps) |
michael@0 | 1537 | return internalProps; |
michael@0 | 1538 | |
michael@0 | 1539 | // Otherwise it's time to fully create them. |
michael@0 | 1540 | internalProps = resolveNumberFormatInternals(internals.lazyData); |
michael@0 | 1541 | setInternalProperties(internals, internalProps); |
michael@0 | 1542 | return internalProps; |
michael@0 | 1543 | } |
michael@0 | 1544 | |
michael@0 | 1545 | |
michael@0 | 1546 | /** |
michael@0 | 1547 | * Initializes an object as a NumberFormat. |
michael@0 | 1548 | * |
michael@0 | 1549 | * This method is complicated a moderate bit by its implementing initialization |
michael@0 | 1550 | * as a *lazy* concept. Everything that must happen now, does -- but we defer |
michael@0 | 1551 | * all the work we can until the object is actually used as a NumberFormat. |
michael@0 | 1552 | * This later work occurs in |resolveNumberFormatInternals|; steps not noted |
michael@0 | 1553 | * here occur there. |
michael@0 | 1554 | * |
michael@0 | 1555 | * Spec: ECMAScript Internationalization API Specification, 11.1.1. |
michael@0 | 1556 | */ |
michael@0 | 1557 | function InitializeNumberFormat(numberFormat, locales, options) { |
michael@0 | 1558 | assert(IsObject(numberFormat), "InitializeNumberFormat"); |
michael@0 | 1559 | |
michael@0 | 1560 | // Step 1. |
michael@0 | 1561 | if (isInitializedIntlObject(numberFormat)) |
michael@0 | 1562 | ThrowError(JSMSG_INTL_OBJECT_REINITED); |
michael@0 | 1563 | |
michael@0 | 1564 | // Step 2. |
michael@0 | 1565 | var internals = initializeIntlObject(numberFormat); |
michael@0 | 1566 | |
michael@0 | 1567 | // Lazy NumberFormat data has the following structure: |
michael@0 | 1568 | // |
michael@0 | 1569 | // { |
michael@0 | 1570 | // requestedLocales: List of locales, |
michael@0 | 1571 | // style: "decimal" / "percent" / "currency", |
michael@0 | 1572 | // |
michael@0 | 1573 | // // fields present only if style === "currency": |
michael@0 | 1574 | // currency: a well-formed currency code (IsWellFormedCurrencyCode), |
michael@0 | 1575 | // currencyDisplay: "code" / "symbol" / "name", |
michael@0 | 1576 | // |
michael@0 | 1577 | // opt: // opt object computed in InitializeNumberFormat |
michael@0 | 1578 | // { |
michael@0 | 1579 | // localeMatcher: "lookup" / "best fit", |
michael@0 | 1580 | // } |
michael@0 | 1581 | // |
michael@0 | 1582 | // minimumIntegerDigits: integer ∈ [1, 21], |
michael@0 | 1583 | // minimumFractionDigits: integer ∈ [0, 20], |
michael@0 | 1584 | // maximumFractionDigits: integer ∈ [0, 20], |
michael@0 | 1585 | // |
michael@0 | 1586 | // // optional |
michael@0 | 1587 | // minimumSignificantDigits: integer ∈ [1, 21], |
michael@0 | 1588 | // maximumSignificantDigits: integer ∈ [1, 21], |
michael@0 | 1589 | // |
michael@0 | 1590 | // useGrouping: true / false, |
michael@0 | 1591 | // } |
michael@0 | 1592 | // |
michael@0 | 1593 | // Note that lazy data is only installed as a final step of initialization, |
michael@0 | 1594 | // so every Collator lazy data object has *all* these properties, never a |
michael@0 | 1595 | // subset of them. |
michael@0 | 1596 | var lazyNumberFormatData = std_Object_create(null); |
michael@0 | 1597 | |
michael@0 | 1598 | // Step 3. |
michael@0 | 1599 | var requestedLocales = CanonicalizeLocaleList(locales); |
michael@0 | 1600 | lazyNumberFormatData.requestedLocales = requestedLocales; |
michael@0 | 1601 | |
michael@0 | 1602 | // Steps 4-5. |
michael@0 | 1603 | // |
michael@0 | 1604 | // If we ever need more speed here at startup, we should try to detect the |
michael@0 | 1605 | // case where |options === undefined| and Object.prototype hasn't been |
michael@0 | 1606 | // mucked with. (|options| is fully consumed in this method, so it's not a |
michael@0 | 1607 | // concern that Object.prototype might be touched between now and when |
michael@0 | 1608 | // |resolveNumberFormatInternals| is called.) For now just keep it simple. |
michael@0 | 1609 | if (options === undefined) |
michael@0 | 1610 | options = {}; |
michael@0 | 1611 | else |
michael@0 | 1612 | options = ToObject(options); |
michael@0 | 1613 | |
michael@0 | 1614 | // Compute options that impact interpretation of locale. |
michael@0 | 1615 | // Step 6. |
michael@0 | 1616 | var opt = new Record(); |
michael@0 | 1617 | lazyNumberFormatData.opt = opt; |
michael@0 | 1618 | |
michael@0 | 1619 | // Steps 7-8. |
michael@0 | 1620 | var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit"); |
michael@0 | 1621 | opt.localeMatcher = matcher; |
michael@0 | 1622 | |
michael@0 | 1623 | // Compute formatting options. |
michael@0 | 1624 | // Step 15. |
michael@0 | 1625 | var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal"); |
michael@0 | 1626 | lazyNumberFormatData.style = s; |
michael@0 | 1627 | |
michael@0 | 1628 | // Steps 17-20. |
michael@0 | 1629 | var c = GetOption(options, "currency", "string", undefined, undefined); |
michael@0 | 1630 | if (c !== undefined && !IsWellFormedCurrencyCode(c)) |
michael@0 | 1631 | ThrowError(JSMSG_INVALID_CURRENCY_CODE, c); |
michael@0 | 1632 | var cDigits; |
michael@0 | 1633 | if (s === "currency") { |
michael@0 | 1634 | if (c === undefined) |
michael@0 | 1635 | ThrowError(JSMSG_UNDEFINED_CURRENCY); |
michael@0 | 1636 | |
michael@0 | 1637 | // Steps 20.a-c. |
michael@0 | 1638 | c = toASCIIUpperCase(c); |
michael@0 | 1639 | lazyNumberFormatData.currency = c; |
michael@0 | 1640 | cDigits = CurrencyDigits(c); |
michael@0 | 1641 | } |
michael@0 | 1642 | |
michael@0 | 1643 | // Step 21. |
michael@0 | 1644 | var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol"); |
michael@0 | 1645 | if (s === "currency") |
michael@0 | 1646 | lazyNumberFormatData.currencyDisplay = cd; |
michael@0 | 1647 | |
michael@0 | 1648 | // Step 23. |
michael@0 | 1649 | var mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1); |
michael@0 | 1650 | lazyNumberFormatData.minimumIntegerDigits = mnid; |
michael@0 | 1651 | |
michael@0 | 1652 | // Steps 25-26. |
michael@0 | 1653 | var mnfdDefault = (s === "currency") ? cDigits : 0; |
michael@0 | 1654 | var mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault); |
michael@0 | 1655 | lazyNumberFormatData.minimumFractionDigits = mnfd; |
michael@0 | 1656 | |
michael@0 | 1657 | // Steps 28-29. |
michael@0 | 1658 | var mxfdDefault; |
michael@0 | 1659 | if (s === "currency") |
michael@0 | 1660 | mxfdDefault = std_Math_max(mnfd, cDigits); |
michael@0 | 1661 | else if (s === "percent") |
michael@0 | 1662 | mxfdDefault = std_Math_max(mnfd, 0); |
michael@0 | 1663 | else |
michael@0 | 1664 | mxfdDefault = std_Math_max(mnfd, 3); |
michael@0 | 1665 | var mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20, mxfdDefault); |
michael@0 | 1666 | lazyNumberFormatData.maximumFractionDigits = mxfd; |
michael@0 | 1667 | |
michael@0 | 1668 | // Steps 31-32. |
michael@0 | 1669 | var mnsd = options.minimumSignificantDigits; |
michael@0 | 1670 | var mxsd = options.maximumSignificantDigits; |
michael@0 | 1671 | |
michael@0 | 1672 | // Step 33. |
michael@0 | 1673 | if (mnsd !== undefined || mxsd !== undefined) { |
michael@0 | 1674 | mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1); |
michael@0 | 1675 | mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21); |
michael@0 | 1676 | lazyNumberFormatData.minimumSignificantDigits = mnsd; |
michael@0 | 1677 | lazyNumberFormatData.maximumSignificantDigits = mxsd; |
michael@0 | 1678 | } |
michael@0 | 1679 | |
michael@0 | 1680 | // Step 34. |
michael@0 | 1681 | var g = GetOption(options, "useGrouping", "boolean", undefined, true); |
michael@0 | 1682 | lazyNumberFormatData.useGrouping = g; |
michael@0 | 1683 | |
michael@0 | 1684 | // Step 43. |
michael@0 | 1685 | // |
michael@0 | 1686 | // We've done everything that must be done now: mark the lazy data as fully |
michael@0 | 1687 | // computed and install it. |
michael@0 | 1688 | setLazyData(internals, "NumberFormat", lazyNumberFormatData); |
michael@0 | 1689 | } |
michael@0 | 1690 | |
michael@0 | 1691 | |
michael@0 | 1692 | /** |
michael@0 | 1693 | * Mapping from currency codes to the number of decimal digits used for them. |
michael@0 | 1694 | * Default is 2 digits. |
michael@0 | 1695 | * |
michael@0 | 1696 | * Spec: ISO 4217 Currency and Funds Code List. |
michael@0 | 1697 | * http://www.currency-iso.org/en/home/tables/table-a1.html |
michael@0 | 1698 | */ |
michael@0 | 1699 | var currencyDigits = { |
michael@0 | 1700 | BHD: 3, |
michael@0 | 1701 | BIF: 0, |
michael@0 | 1702 | BYR: 0, |
michael@0 | 1703 | CLF: 0, |
michael@0 | 1704 | CLP: 0, |
michael@0 | 1705 | DJF: 0, |
michael@0 | 1706 | IQD: 3, |
michael@0 | 1707 | GNF: 0, |
michael@0 | 1708 | ISK: 0, |
michael@0 | 1709 | JOD: 3, |
michael@0 | 1710 | JPY: 0, |
michael@0 | 1711 | KMF: 0, |
michael@0 | 1712 | KRW: 0, |
michael@0 | 1713 | KWD: 3, |
michael@0 | 1714 | LYD: 3, |
michael@0 | 1715 | OMR: 3, |
michael@0 | 1716 | PYG: 0, |
michael@0 | 1717 | RWF: 0, |
michael@0 | 1718 | TND: 3, |
michael@0 | 1719 | UGX: 0, |
michael@0 | 1720 | UYI: 0, |
michael@0 | 1721 | VND: 0, |
michael@0 | 1722 | VUV: 0, |
michael@0 | 1723 | XAF: 0, |
michael@0 | 1724 | XOF: 0, |
michael@0 | 1725 | XPF: 0 |
michael@0 | 1726 | }; |
michael@0 | 1727 | |
michael@0 | 1728 | |
michael@0 | 1729 | /** |
michael@0 | 1730 | * Returns the number of decimal digits to be used for the given currency. |
michael@0 | 1731 | * |
michael@0 | 1732 | * Spec: ECMAScript Internationalization API Specification, 11.1.1. |
michael@0 | 1733 | */ |
michael@0 | 1734 | function CurrencyDigits(currency) { |
michael@0 | 1735 | assert(typeof currency === "string", "CurrencyDigits"); |
michael@0 | 1736 | assert(regexp_test_no_statics(/^[A-Z]{3}$/, currency), "CurrencyDigits"); |
michael@0 | 1737 | |
michael@0 | 1738 | if (callFunction(std_Object_hasOwnProperty, currencyDigits, currency)) |
michael@0 | 1739 | return currencyDigits[currency]; |
michael@0 | 1740 | return 2; |
michael@0 | 1741 | } |
michael@0 | 1742 | |
michael@0 | 1743 | |
michael@0 | 1744 | /** |
michael@0 | 1745 | * Returns the subset of the given locale list for which this locale list has a |
michael@0 | 1746 | * matching (possibly fallback) locale. Locales appear in the same order in the |
michael@0 | 1747 | * returned list as in the input list. |
michael@0 | 1748 | * |
michael@0 | 1749 | * Spec: ECMAScript Internationalization API Specification, 11.2.2. |
michael@0 | 1750 | */ |
michael@0 | 1751 | function Intl_NumberFormat_supportedLocalesOf(locales /*, options*/) { |
michael@0 | 1752 | var options = arguments.length > 1 ? arguments[1] : undefined; |
michael@0 | 1753 | |
michael@0 | 1754 | var availableLocales = numberFormatInternalProperties.availableLocales(); |
michael@0 | 1755 | var requestedLocales = CanonicalizeLocaleList(locales); |
michael@0 | 1756 | return SupportedLocales(availableLocales, requestedLocales, options); |
michael@0 | 1757 | } |
michael@0 | 1758 | |
michael@0 | 1759 | |
michael@0 | 1760 | function getNumberingSystems(locale) { |
michael@0 | 1761 | // ICU doesn't have an API to determine the set of numbering systems |
michael@0 | 1762 | // supported for a locale; it generally pretends that any numbering system |
michael@0 | 1763 | // can be used with any locale. Supporting a decimal numbering system |
michael@0 | 1764 | // (where only the digits are replaced) is easy, so we offer them all here. |
michael@0 | 1765 | // Algorithmic numbering systems are typically tied to one locale, so for |
michael@0 | 1766 | // lack of information we don't offer them. To increase chances that |
michael@0 | 1767 | // other software will process output correctly, we further restrict to |
michael@0 | 1768 | // those decimal numbering systems explicitly listed in table 2 of |
michael@0 | 1769 | // the ECMAScript Internationalization API Specification, 11.3.2, which |
michael@0 | 1770 | // in turn are those with full specifications in version 21 of Unicode |
michael@0 | 1771 | // Technical Standard #35 using digits that were defined in Unicode 5.0, |
michael@0 | 1772 | // the Unicode version supported in Windows Vista. |
michael@0 | 1773 | // The one thing we can find out from ICU is the default numbering system |
michael@0 | 1774 | // for a locale. |
michael@0 | 1775 | var defaultNumberingSystem = intl_numberingSystem(locale); |
michael@0 | 1776 | return [ |
michael@0 | 1777 | defaultNumberingSystem, |
michael@0 | 1778 | "arab", "arabext", "bali", "beng", "deva", |
michael@0 | 1779 | "fullwide", "gujr", "guru", "hanidec", "khmr", |
michael@0 | 1780 | "knda", "laoo", "latn", "limb", "mlym", |
michael@0 | 1781 | "mong", "mymr", "orya", "tamldec", "telu", |
michael@0 | 1782 | "thai", "tibt" |
michael@0 | 1783 | ]; |
michael@0 | 1784 | } |
michael@0 | 1785 | |
michael@0 | 1786 | |
michael@0 | 1787 | function numberFormatLocaleData(locale) { |
michael@0 | 1788 | return { |
michael@0 | 1789 | nu: getNumberingSystems(locale) |
michael@0 | 1790 | }; |
michael@0 | 1791 | } |
michael@0 | 1792 | |
michael@0 | 1793 | |
michael@0 | 1794 | /** |
michael@0 | 1795 | * Function to be bound and returned by Intl.NumberFormat.prototype.format. |
michael@0 | 1796 | * |
michael@0 | 1797 | * Spec: ECMAScript Internationalization API Specification, 11.3.2. |
michael@0 | 1798 | */ |
michael@0 | 1799 | function numberFormatFormatToBind(value) { |
michael@0 | 1800 | // Steps 1.a.i implemented by ECMAScript declaration binding instantiation, |
michael@0 | 1801 | // ES5.1 10.5, step 4.d.ii. |
michael@0 | 1802 | |
michael@0 | 1803 | // Step 1.a.ii-iii. |
michael@0 | 1804 | var x = ToNumber(value); |
michael@0 | 1805 | return intl_FormatNumber(this, x); |
michael@0 | 1806 | } |
michael@0 | 1807 | |
michael@0 | 1808 | |
michael@0 | 1809 | /** |
michael@0 | 1810 | * Returns a function bound to this NumberFormat that returns a String value |
michael@0 | 1811 | * representing the result of calling ToNumber(value) according to the |
michael@0 | 1812 | * effective locale and the formatting options of this NumberFormat. |
michael@0 | 1813 | * |
michael@0 | 1814 | * Spec: ECMAScript Internationalization API Specification, 11.3.2. |
michael@0 | 1815 | */ |
michael@0 | 1816 | function Intl_NumberFormat_format_get() { |
michael@0 | 1817 | // Check "this NumberFormat object" per introduction of section 11.3. |
michael@0 | 1818 | var internals = getNumberFormatInternals(this, "format"); |
michael@0 | 1819 | |
michael@0 | 1820 | // Step 1. |
michael@0 | 1821 | if (internals.boundFormat === undefined) { |
michael@0 | 1822 | // Step 1.a. |
michael@0 | 1823 | var F = numberFormatFormatToBind; |
michael@0 | 1824 | |
michael@0 | 1825 | // Step 1.b-d. |
michael@0 | 1826 | var bf = callFunction(std_Function_bind, F, this); |
michael@0 | 1827 | internals.boundFormat = bf; |
michael@0 | 1828 | } |
michael@0 | 1829 | // Step 2. |
michael@0 | 1830 | return internals.boundFormat; |
michael@0 | 1831 | } |
michael@0 | 1832 | |
michael@0 | 1833 | |
michael@0 | 1834 | /** |
michael@0 | 1835 | * Returns the resolved options for a NumberFormat object. |
michael@0 | 1836 | * |
michael@0 | 1837 | * Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4. |
michael@0 | 1838 | */ |
michael@0 | 1839 | function Intl_NumberFormat_resolvedOptions() { |
michael@0 | 1840 | // Check "this NumberFormat object" per introduction of section 11.3. |
michael@0 | 1841 | var internals = getNumberFormatInternals(this, "resolvedOptions"); |
michael@0 | 1842 | |
michael@0 | 1843 | var result = { |
michael@0 | 1844 | locale: internals.locale, |
michael@0 | 1845 | numberingSystem: internals.numberingSystem, |
michael@0 | 1846 | style: internals.style, |
michael@0 | 1847 | minimumIntegerDigits: internals.minimumIntegerDigits, |
michael@0 | 1848 | minimumFractionDigits: internals.minimumFractionDigits, |
michael@0 | 1849 | maximumFractionDigits: internals.maximumFractionDigits, |
michael@0 | 1850 | useGrouping: internals.useGrouping |
michael@0 | 1851 | }; |
michael@0 | 1852 | var optionalProperties = [ |
michael@0 | 1853 | "currency", |
michael@0 | 1854 | "currencyDisplay", |
michael@0 | 1855 | "minimumSignificantDigits", |
michael@0 | 1856 | "maximumSignificantDigits" |
michael@0 | 1857 | ]; |
michael@0 | 1858 | for (var i = 0; i < optionalProperties.length; i++) { |
michael@0 | 1859 | var p = optionalProperties[i]; |
michael@0 | 1860 | if (callFunction(std_Object_hasOwnProperty, internals, p)) |
michael@0 | 1861 | defineProperty(result, p, internals[p]); |
michael@0 | 1862 | } |
michael@0 | 1863 | return result; |
michael@0 | 1864 | } |
michael@0 | 1865 | |
michael@0 | 1866 | |
michael@0 | 1867 | /********** Intl.DateTimeFormat **********/ |
michael@0 | 1868 | |
michael@0 | 1869 | |
michael@0 | 1870 | /** |
michael@0 | 1871 | * Compute an internal properties object from |lazyDateTimeFormatData|. |
michael@0 | 1872 | */ |
michael@0 | 1873 | function resolveDateTimeFormatInternals(lazyDateTimeFormatData) { |
michael@0 | 1874 | assert(IsObject(lazyDateTimeFormatData), "lazy data not an object?"); |
michael@0 | 1875 | |
michael@0 | 1876 | // Lazy DateTimeFormat data has the following structure: |
michael@0 | 1877 | // |
michael@0 | 1878 | // { |
michael@0 | 1879 | // requestedLocales: List of locales, |
michael@0 | 1880 | // |
michael@0 | 1881 | // localeOpt: // *first* opt computed in InitializeDateTimeFormat |
michael@0 | 1882 | // { |
michael@0 | 1883 | // localeMatcher: "lookup" / "best fit", |
michael@0 | 1884 | // |
michael@0 | 1885 | // hour12: true / false, // optional |
michael@0 | 1886 | // } |
michael@0 | 1887 | // |
michael@0 | 1888 | // timeZone: undefined / "UTC", |
michael@0 | 1889 | // |
michael@0 | 1890 | // formatOpt: // *second* opt computed in InitializeDateTimeFormat |
michael@0 | 1891 | // { |
michael@0 | 1892 | // // all the properties/values listed in Table 3 |
michael@0 | 1893 | // // (weekday, era, year, month, day, &c.) |
michael@0 | 1894 | // } |
michael@0 | 1895 | // |
michael@0 | 1896 | // formatMatcher: "basic" / "best fit", |
michael@0 | 1897 | // } |
michael@0 | 1898 | // |
michael@0 | 1899 | // Note that lazy data is only installed as a final step of initialization, |
michael@0 | 1900 | // so every DateTimeFormat lazy data object has *all* these properties, |
michael@0 | 1901 | // never a subset of them. |
michael@0 | 1902 | |
michael@0 | 1903 | var internalProps = std_Object_create(null); |
michael@0 | 1904 | |
michael@0 | 1905 | // Compute effective locale. |
michael@0 | 1906 | // Step 8. |
michael@0 | 1907 | var DateTimeFormat = dateTimeFormatInternalProperties; |
michael@0 | 1908 | |
michael@0 | 1909 | // Step 9. |
michael@0 | 1910 | var localeData = DateTimeFormat.localeData; |
michael@0 | 1911 | |
michael@0 | 1912 | // Step 10. |
michael@0 | 1913 | var r = ResolveLocale(DateTimeFormat.availableLocales(), |
michael@0 | 1914 | lazyDateTimeFormatData.requestedLocales, |
michael@0 | 1915 | lazyDateTimeFormatData.localeOpt, |
michael@0 | 1916 | DateTimeFormat.relevantExtensionKeys, |
michael@0 | 1917 | localeData); |
michael@0 | 1918 | |
michael@0 | 1919 | // Steps 11-13. |
michael@0 | 1920 | internalProps.locale = r.locale; |
michael@0 | 1921 | internalProps.calendar = r.ca; |
michael@0 | 1922 | internalProps.numberingSystem = r.nu; |
michael@0 | 1923 | |
michael@0 | 1924 | // Compute formatting options. |
michael@0 | 1925 | // Step 14. |
michael@0 | 1926 | var dataLocale = r.dataLocale; |
michael@0 | 1927 | |
michael@0 | 1928 | // Steps 15-17. |
michael@0 | 1929 | internalProps.timeZone = lazyDateTimeFormatData.timeZone; |
michael@0 | 1930 | |
michael@0 | 1931 | // Step 18. |
michael@0 | 1932 | var formatOpt = lazyDateTimeFormatData.formatOpt; |
michael@0 | 1933 | |
michael@0 | 1934 | // Steps 27-28, more or less - see comment after this function. |
michael@0 | 1935 | var pattern = toBestICUPattern(dataLocale, formatOpt); |
michael@0 | 1936 | |
michael@0 | 1937 | // Step 29. |
michael@0 | 1938 | internalProps.pattern = pattern; |
michael@0 | 1939 | |
michael@0 | 1940 | // Step 30. |
michael@0 | 1941 | internalProps.boundFormat = undefined; |
michael@0 | 1942 | |
michael@0 | 1943 | // The caller is responsible for associating |internalProps| with the right |
michael@0 | 1944 | // object using |setInternalProperties|. |
michael@0 | 1945 | return internalProps; |
michael@0 | 1946 | } |
michael@0 | 1947 | |
michael@0 | 1948 | |
michael@0 | 1949 | /** |
michael@0 | 1950 | * Returns an object containing the DateTimeFormat internal properties of |obj|, |
michael@0 | 1951 | * or throws a TypeError if |obj| isn't DateTimeFormat-initialized. |
michael@0 | 1952 | */ |
michael@0 | 1953 | function getDateTimeFormatInternals(obj, methodName) { |
michael@0 | 1954 | var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName); |
michael@0 | 1955 | assert(internals.type === "DateTimeFormat", "bad type escaped getIntlObjectInternals"); |
michael@0 | 1956 | |
michael@0 | 1957 | // If internal properties have already been computed, use them. |
michael@0 | 1958 | var internalProps = maybeInternalProperties(internals); |
michael@0 | 1959 | if (internalProps) |
michael@0 | 1960 | return internalProps; |
michael@0 | 1961 | |
michael@0 | 1962 | // Otherwise it's time to fully create them. |
michael@0 | 1963 | internalProps = resolveDateTimeFormatInternals(internals.lazyData); |
michael@0 | 1964 | setInternalProperties(internals, internalProps); |
michael@0 | 1965 | return internalProps; |
michael@0 | 1966 | } |
michael@0 | 1967 | |
michael@0 | 1968 | /** |
michael@0 | 1969 | * Components of date and time formats and their values. |
michael@0 | 1970 | * |
michael@0 | 1971 | * Spec: ECMAScript Internationalization API Specification, 12.1.1. |
michael@0 | 1972 | */ |
michael@0 | 1973 | var dateTimeComponentValues = { |
michael@0 | 1974 | weekday: ["narrow", "short", "long"], |
michael@0 | 1975 | era: ["narrow", "short", "long"], |
michael@0 | 1976 | year: ["2-digit", "numeric"], |
michael@0 | 1977 | month: ["2-digit", "numeric", "narrow", "short", "long"], |
michael@0 | 1978 | day: ["2-digit", "numeric"], |
michael@0 | 1979 | hour: ["2-digit", "numeric"], |
michael@0 | 1980 | minute: ["2-digit", "numeric"], |
michael@0 | 1981 | second: ["2-digit", "numeric"], |
michael@0 | 1982 | timeZoneName: ["short", "long"] |
michael@0 | 1983 | }; |
michael@0 | 1984 | |
michael@0 | 1985 | |
michael@0 | 1986 | var dateTimeComponents = std_Object_getOwnPropertyNames(dateTimeComponentValues); |
michael@0 | 1987 | |
michael@0 | 1988 | |
michael@0 | 1989 | /** |
michael@0 | 1990 | * Initializes an object as a DateTimeFormat. |
michael@0 | 1991 | * |
michael@0 | 1992 | * This method is complicated a moderate bit by its implementing initialization |
michael@0 | 1993 | * as a *lazy* concept. Everything that must happen now, does -- but we defer |
michael@0 | 1994 | * all the work we can until the object is actually used as a DateTimeFormat. |
michael@0 | 1995 | * This later work occurs in |resolveDateTimeFormatInternals|; steps not noted |
michael@0 | 1996 | * here occur there. |
michael@0 | 1997 | * |
michael@0 | 1998 | * Spec: ECMAScript Internationalization API Specification, 12.1.1. |
michael@0 | 1999 | */ |
michael@0 | 2000 | function InitializeDateTimeFormat(dateTimeFormat, locales, options) { |
michael@0 | 2001 | assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat"); |
michael@0 | 2002 | |
michael@0 | 2003 | // Step 1. |
michael@0 | 2004 | if (isInitializedIntlObject(dateTimeFormat)) |
michael@0 | 2005 | ThrowError(JSMSG_INTL_OBJECT_REINITED); |
michael@0 | 2006 | |
michael@0 | 2007 | // Step 2. |
michael@0 | 2008 | var internals = initializeIntlObject(dateTimeFormat); |
michael@0 | 2009 | |
michael@0 | 2010 | // Lazy DateTimeFormat data has the following structure: |
michael@0 | 2011 | // |
michael@0 | 2012 | // { |
michael@0 | 2013 | // requestedLocales: List of locales, |
michael@0 | 2014 | // |
michael@0 | 2015 | // localeOpt: // *first* opt computed in InitializeDateTimeFormat |
michael@0 | 2016 | // { |
michael@0 | 2017 | // localeMatcher: "lookup" / "best fit", |
michael@0 | 2018 | // } |
michael@0 | 2019 | // |
michael@0 | 2020 | // timeZone: undefined / "UTC", |
michael@0 | 2021 | // |
michael@0 | 2022 | // formatOpt: // *second* opt computed in InitializeDateTimeFormat |
michael@0 | 2023 | // { |
michael@0 | 2024 | // // all the properties/values listed in Table 3 |
michael@0 | 2025 | // // (weekday, era, year, month, day, &c.) |
michael@0 | 2026 | // |
michael@0 | 2027 | // hour12: true / false // optional |
michael@0 | 2028 | // } |
michael@0 | 2029 | // |
michael@0 | 2030 | // formatMatcher: "basic" / "best fit", |
michael@0 | 2031 | // } |
michael@0 | 2032 | // |
michael@0 | 2033 | // Note that lazy data is only installed as a final step of initialization, |
michael@0 | 2034 | // so every DateTimeFormat lazy data object has *all* these properties, |
michael@0 | 2035 | // never a subset of them. |
michael@0 | 2036 | var lazyDateTimeFormatData = std_Object_create(null); |
michael@0 | 2037 | |
michael@0 | 2038 | // Step 3. |
michael@0 | 2039 | var requestedLocales = CanonicalizeLocaleList(locales); |
michael@0 | 2040 | lazyDateTimeFormatData.requestedLocales = requestedLocales; |
michael@0 | 2041 | |
michael@0 | 2042 | // Step 4. |
michael@0 | 2043 | options = ToDateTimeOptions(options, "any", "date"); |
michael@0 | 2044 | |
michael@0 | 2045 | // Compute options that impact interpretation of locale. |
michael@0 | 2046 | // Step 5. |
michael@0 | 2047 | var localeOpt = new Record(); |
michael@0 | 2048 | lazyDateTimeFormatData.localeOpt = localeOpt; |
michael@0 | 2049 | |
michael@0 | 2050 | // Steps 6-7. |
michael@0 | 2051 | var localeMatcher = |
michael@0 | 2052 | GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], |
michael@0 | 2053 | "best fit"); |
michael@0 | 2054 | localeOpt.localeMatcher = localeMatcher; |
michael@0 | 2055 | |
michael@0 | 2056 | // Steps 15-17. |
michael@0 | 2057 | var tz = options.timeZone; |
michael@0 | 2058 | if (tz !== undefined) { |
michael@0 | 2059 | tz = toASCIIUpperCase(ToString(tz)); |
michael@0 | 2060 | if (tz !== "UTC") |
michael@0 | 2061 | ThrowError(JSMSG_INVALID_TIME_ZONE, tz); |
michael@0 | 2062 | } |
michael@0 | 2063 | lazyDateTimeFormatData.timeZone = tz; |
michael@0 | 2064 | |
michael@0 | 2065 | // Step 18. |
michael@0 | 2066 | var formatOpt = new Record(); |
michael@0 | 2067 | lazyDateTimeFormatData.formatOpt = formatOpt; |
michael@0 | 2068 | |
michael@0 | 2069 | // Step 19. |
michael@0 | 2070 | var i, prop; |
michael@0 | 2071 | for (i = 0; i < dateTimeComponents.length; i++) { |
michael@0 | 2072 | prop = dateTimeComponents[i]; |
michael@0 | 2073 | var value = GetOption(options, prop, "string", dateTimeComponentValues[prop], undefined); |
michael@0 | 2074 | formatOpt[prop] = value; |
michael@0 | 2075 | } |
michael@0 | 2076 | |
michael@0 | 2077 | // Steps 20-21 provided by ICU - see comment after this function. |
michael@0 | 2078 | |
michael@0 | 2079 | // Step 22. |
michael@0 | 2080 | // |
michael@0 | 2081 | // For some reason (ICU not exposing enough interface?) we drop the |
michael@0 | 2082 | // requested format matcher on the floor after this. In any case, even if |
michael@0 | 2083 | // doing so is justified, we have to do this work here in case it triggers |
michael@0 | 2084 | // getters or similar. |
michael@0 | 2085 | var formatMatcher = |
michael@0 | 2086 | GetOption(options, "formatMatcher", "string", ["basic", "best fit"], |
michael@0 | 2087 | "best fit"); |
michael@0 | 2088 | |
michael@0 | 2089 | // Steps 23-25 provided by ICU, more or less - see comment after this function. |
michael@0 | 2090 | |
michael@0 | 2091 | // Step 26. |
michael@0 | 2092 | var hr12 = GetOption(options, "hour12", "boolean", undefined, undefined); |
michael@0 | 2093 | |
michael@0 | 2094 | // Pass hr12 on to ICU. |
michael@0 | 2095 | if (hr12 !== undefined) |
michael@0 | 2096 | formatOpt.hour12 = hr12; |
michael@0 | 2097 | |
michael@0 | 2098 | // Step 31. |
michael@0 | 2099 | // |
michael@0 | 2100 | // We've done everything that must be done now: mark the lazy data as fully |
michael@0 | 2101 | // computed and install it. |
michael@0 | 2102 | setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData); |
michael@0 | 2103 | } |
michael@0 | 2104 | |
michael@0 | 2105 | |
michael@0 | 2106 | // Intl.DateTimeFormat and ICU skeletons and patterns |
michael@0 | 2107 | // ================================================== |
michael@0 | 2108 | // |
michael@0 | 2109 | // Different locales have different ways to display dates using the same |
michael@0 | 2110 | // basic components. For example, en-US might use "Sept. 24, 2012" while |
michael@0 | 2111 | // fr-FR might use "24 Sept. 2012". The intent of Intl.DateTimeFormat is to |
michael@0 | 2112 | // permit production of a format for the locale that best matches the |
michael@0 | 2113 | // set of date-time components and their desired representation as specified |
michael@0 | 2114 | // by the API client. |
michael@0 | 2115 | // |
michael@0 | 2116 | // ICU supports specification of date and time formats in three ways: |
michael@0 | 2117 | // |
michael@0 | 2118 | // 1) A style is just one of the identifiers FULL, LONG, MEDIUM, or SHORT. |
michael@0 | 2119 | // The date-time components included in each style and their representation |
michael@0 | 2120 | // are defined by ICU using CLDR locale data (CLDR is the Unicode |
michael@0 | 2121 | // Consortium's Common Locale Data Repository). |
michael@0 | 2122 | // |
michael@0 | 2123 | // 2) A skeleton is a string specifying which date-time components to include, |
michael@0 | 2124 | // and which representations to use for them. For example, "yyyyMMMMdd" |
michael@0 | 2125 | // specifies a year with at least four digits, a full month name, and a |
michael@0 | 2126 | // two-digit day. It does not specify in which order the components appear, |
michael@0 | 2127 | // how they are separated, the localized strings for textual components |
michael@0 | 2128 | // (such as weekday or month), whether the month is in format or |
michael@0 | 2129 | // stand-alone form¹, or the numbering system used for numeric components. |
michael@0 | 2130 | // All that information is filled in by ICU using CLDR locale data. |
michael@0 | 2131 | // ¹ The format form is the one used in formatted strings that include a |
michael@0 | 2132 | // day; the stand-alone form is used when not including days, e.g., in |
michael@0 | 2133 | // calendar headers. The two forms differ at least in some Slavic languages, |
michael@0 | 2134 | // e.g. Russian: "22 марта 2013 г." vs. "Март 2013". |
michael@0 | 2135 | // |
michael@0 | 2136 | // 3) A pattern is a string specifying which date-time components to include, |
michael@0 | 2137 | // in which order, with which separators, in which grammatical case. For |
michael@0 | 2138 | // example, "EEEE, d MMMM y" specifies the full localized weekday name, |
michael@0 | 2139 | // followed by comma and space, followed by the day, followed by space, |
michael@0 | 2140 | // followed by the full month name in format form, followed by space, |
michael@0 | 2141 | // followed by the full year. It |
michael@0 | 2142 | // still does not specify localized strings for textual components and the |
michael@0 | 2143 | // numbering system - these are determined by ICU using CLDR locale data or |
michael@0 | 2144 | // possibly API parameters. |
michael@0 | 2145 | // |
michael@0 | 2146 | // All actual formatting in ICU is done with patterns; styles and skeletons |
michael@0 | 2147 | // have to be mapped to patterns before processing. |
michael@0 | 2148 | // |
michael@0 | 2149 | // The options of DateTimeFormat most closely correspond to ICU skeletons. This |
michael@0 | 2150 | // implementation therefore, in the toBestICUPattern function, converts |
michael@0 | 2151 | // DateTimeFormat options to ICU skeletons, and then lets ICU map skeletons to |
michael@0 | 2152 | // actual ICU patterns. The pattern may not directly correspond to what the |
michael@0 | 2153 | // skeleton requests, as the mapper (UDateTimePatternGenerator) is constrained |
michael@0 | 2154 | // by the available locale data for the locale. The resulting ICU pattern is |
michael@0 | 2155 | // kept as the DateTimeFormat's [[pattern]] internal property and passed to ICU |
michael@0 | 2156 | // in the format method. |
michael@0 | 2157 | // |
michael@0 | 2158 | // An ICU pattern represents the information of the following DateTimeFormat |
michael@0 | 2159 | // internal properties described in the specification, which therefore don't |
michael@0 | 2160 | // exist separately in the implementation: |
michael@0 | 2161 | // - [[weekday]], [[era]], [[year]], [[month]], [[day]], [[hour]], [[minute]], |
michael@0 | 2162 | // [[second]], [[timeZoneName]] |
michael@0 | 2163 | // - [[hour12]] |
michael@0 | 2164 | // - [[hourNo0]] |
michael@0 | 2165 | // When needed for the resolvedOptions method, the resolveICUPattern function |
michael@0 | 2166 | // maps the instance's ICU pattern back to the specified properties of the |
michael@0 | 2167 | // object returned by resolvedOptions. |
michael@0 | 2168 | // |
michael@0 | 2169 | // ICU date-time skeletons and patterns aren't fully documented in the ICU |
michael@0 | 2170 | // documentation (see http://bugs.icu-project.org/trac/ticket/9627). The best |
michael@0 | 2171 | // documentation at this point is in UTR 35: |
michael@0 | 2172 | // http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns |
michael@0 | 2173 | |
michael@0 | 2174 | |
michael@0 | 2175 | /** |
michael@0 | 2176 | * Returns an ICU pattern string for the given locale and representing the |
michael@0 | 2177 | * specified options as closely as possible given available locale data. |
michael@0 | 2178 | */ |
michael@0 | 2179 | function toBestICUPattern(locale, options) { |
michael@0 | 2180 | // Create an ICU skeleton representing the specified options. See |
michael@0 | 2181 | // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table |
michael@0 | 2182 | var skeleton = ""; |
michael@0 | 2183 | switch (options.weekday) { |
michael@0 | 2184 | case "narrow": |
michael@0 | 2185 | skeleton += "EEEEE"; |
michael@0 | 2186 | break; |
michael@0 | 2187 | case "short": |
michael@0 | 2188 | skeleton += "E"; |
michael@0 | 2189 | break; |
michael@0 | 2190 | case "long": |
michael@0 | 2191 | skeleton += "EEEE"; |
michael@0 | 2192 | } |
michael@0 | 2193 | switch (options.era) { |
michael@0 | 2194 | case "narrow": |
michael@0 | 2195 | skeleton += "GGGGG"; |
michael@0 | 2196 | break; |
michael@0 | 2197 | case "short": |
michael@0 | 2198 | skeleton += "G"; |
michael@0 | 2199 | break; |
michael@0 | 2200 | case "long": |
michael@0 | 2201 | skeleton += "GGGG"; |
michael@0 | 2202 | break; |
michael@0 | 2203 | } |
michael@0 | 2204 | switch (options.year) { |
michael@0 | 2205 | case "2-digit": |
michael@0 | 2206 | skeleton += "yy"; |
michael@0 | 2207 | break; |
michael@0 | 2208 | case "numeric": |
michael@0 | 2209 | skeleton += "y"; |
michael@0 | 2210 | break; |
michael@0 | 2211 | } |
michael@0 | 2212 | switch (options.month) { |
michael@0 | 2213 | case "2-digit": |
michael@0 | 2214 | skeleton += "MM"; |
michael@0 | 2215 | break; |
michael@0 | 2216 | case "numeric": |
michael@0 | 2217 | skeleton += "M"; |
michael@0 | 2218 | break; |
michael@0 | 2219 | case "narrow": |
michael@0 | 2220 | skeleton += "MMMMM"; |
michael@0 | 2221 | break; |
michael@0 | 2222 | case "short": |
michael@0 | 2223 | skeleton += "MMM"; |
michael@0 | 2224 | break; |
michael@0 | 2225 | case "long": |
michael@0 | 2226 | skeleton += "MMMM"; |
michael@0 | 2227 | break; |
michael@0 | 2228 | } |
michael@0 | 2229 | switch (options.day) { |
michael@0 | 2230 | case "2-digit": |
michael@0 | 2231 | skeleton += "dd"; |
michael@0 | 2232 | break; |
michael@0 | 2233 | case "numeric": |
michael@0 | 2234 | skeleton += "d"; |
michael@0 | 2235 | break; |
michael@0 | 2236 | } |
michael@0 | 2237 | var hourSkeletonChar = "j"; |
michael@0 | 2238 | if (options.hour12 !== undefined) { |
michael@0 | 2239 | if (options.hour12) |
michael@0 | 2240 | hourSkeletonChar = "h"; |
michael@0 | 2241 | else |
michael@0 | 2242 | hourSkeletonChar = "H"; |
michael@0 | 2243 | } |
michael@0 | 2244 | switch (options.hour) { |
michael@0 | 2245 | case "2-digit": |
michael@0 | 2246 | skeleton += hourSkeletonChar + hourSkeletonChar; |
michael@0 | 2247 | break; |
michael@0 | 2248 | case "numeric": |
michael@0 | 2249 | skeleton += hourSkeletonChar; |
michael@0 | 2250 | break; |
michael@0 | 2251 | } |
michael@0 | 2252 | switch (options.minute) { |
michael@0 | 2253 | case "2-digit": |
michael@0 | 2254 | skeleton += "mm"; |
michael@0 | 2255 | break; |
michael@0 | 2256 | case "numeric": |
michael@0 | 2257 | skeleton += "m"; |
michael@0 | 2258 | break; |
michael@0 | 2259 | } |
michael@0 | 2260 | switch (options.second) { |
michael@0 | 2261 | case "2-digit": |
michael@0 | 2262 | skeleton += "ss"; |
michael@0 | 2263 | break; |
michael@0 | 2264 | case "numeric": |
michael@0 | 2265 | skeleton += "s"; |
michael@0 | 2266 | break; |
michael@0 | 2267 | } |
michael@0 | 2268 | switch (options.timeZoneName) { |
michael@0 | 2269 | case "short": |
michael@0 | 2270 | skeleton += "z"; |
michael@0 | 2271 | break; |
michael@0 | 2272 | case "long": |
michael@0 | 2273 | skeleton += "zzzz"; |
michael@0 | 2274 | break; |
michael@0 | 2275 | } |
michael@0 | 2276 | |
michael@0 | 2277 | // Let ICU convert the ICU skeleton to an ICU pattern for the given locale. |
michael@0 | 2278 | return intl_patternForSkeleton(locale, skeleton); |
michael@0 | 2279 | } |
michael@0 | 2280 | |
michael@0 | 2281 | |
michael@0 | 2282 | /** |
michael@0 | 2283 | * Returns a new options object that includes the provided options (if any) |
michael@0 | 2284 | * and fills in default components if required components are not defined. |
michael@0 | 2285 | * Required can be "date", "time", or "any". |
michael@0 | 2286 | * Defaults can be "date", "time", or "all". |
michael@0 | 2287 | * |
michael@0 | 2288 | * Spec: ECMAScript Internationalization API Specification, 12.1.1. |
michael@0 | 2289 | */ |
michael@0 | 2290 | function ToDateTimeOptions(options, required, defaults) { |
michael@0 | 2291 | assert(typeof required === "string", "ToDateTimeOptions"); |
michael@0 | 2292 | assert(typeof defaults === "string", "ToDateTimeOptions"); |
michael@0 | 2293 | |
michael@0 | 2294 | // Steps 1-3. |
michael@0 | 2295 | if (options === undefined) |
michael@0 | 2296 | options = null; |
michael@0 | 2297 | else |
michael@0 | 2298 | options = ToObject(options); |
michael@0 | 2299 | options = std_Object_create(options); |
michael@0 | 2300 | |
michael@0 | 2301 | // Step 4. |
michael@0 | 2302 | var needDefaults = true; |
michael@0 | 2303 | |
michael@0 | 2304 | // Step 5. |
michael@0 | 2305 | if ((required === "date" || required === "any") && |
michael@0 | 2306 | (options.weekday !== undefined || options.year !== undefined || |
michael@0 | 2307 | options.month !== undefined || options.day !== undefined)) |
michael@0 | 2308 | { |
michael@0 | 2309 | needDefaults = false; |
michael@0 | 2310 | } |
michael@0 | 2311 | |
michael@0 | 2312 | // Step 6. |
michael@0 | 2313 | if ((required === "time" || required === "any") && |
michael@0 | 2314 | (options.hour !== undefined || options.minute !== undefined || |
michael@0 | 2315 | options.second !== undefined)) |
michael@0 | 2316 | { |
michael@0 | 2317 | needDefaults = false; |
michael@0 | 2318 | } |
michael@0 | 2319 | |
michael@0 | 2320 | // Step 7. |
michael@0 | 2321 | if (needDefaults && (defaults === "date" || defaults === "all")) { |
michael@0 | 2322 | // The specification says to call [[DefineOwnProperty]] with false for |
michael@0 | 2323 | // the Throw parameter, while Object.defineProperty uses true. For the |
michael@0 | 2324 | // calls here, the difference doesn't matter because we're adding |
michael@0 | 2325 | // properties to a new object. |
michael@0 | 2326 | defineProperty(options, "year", "numeric"); |
michael@0 | 2327 | defineProperty(options, "month", "numeric"); |
michael@0 | 2328 | defineProperty(options, "day", "numeric"); |
michael@0 | 2329 | } |
michael@0 | 2330 | |
michael@0 | 2331 | // Step 8. |
michael@0 | 2332 | if (needDefaults && (defaults === "time" || defaults === "all")) { |
michael@0 | 2333 | // See comment for step 7. |
michael@0 | 2334 | defineProperty(options, "hour", "numeric"); |
michael@0 | 2335 | defineProperty(options, "minute", "numeric"); |
michael@0 | 2336 | defineProperty(options, "second", "numeric"); |
michael@0 | 2337 | } |
michael@0 | 2338 | |
michael@0 | 2339 | // Step 9. |
michael@0 | 2340 | return options; |
michael@0 | 2341 | } |
michael@0 | 2342 | |
michael@0 | 2343 | |
michael@0 | 2344 | /** |
michael@0 | 2345 | * Compares the date and time components requested by options with the available |
michael@0 | 2346 | * date and time formats in formats, and selects the best match according |
michael@0 | 2347 | * to a specified basic matching algorithm. |
michael@0 | 2348 | * |
michael@0 | 2349 | * Spec: ECMAScript Internationalization API Specification, 12.1.1. |
michael@0 | 2350 | */ |
michael@0 | 2351 | function BasicFormatMatcher(options, formats) { |
michael@0 | 2352 | // Steps 1-6. |
michael@0 | 2353 | var removalPenalty = 120, |
michael@0 | 2354 | additionPenalty = 20, |
michael@0 | 2355 | longLessPenalty = 8, |
michael@0 | 2356 | longMorePenalty = 6, |
michael@0 | 2357 | shortLessPenalty = 6, |
michael@0 | 2358 | shortMorePenalty = 3; |
michael@0 | 2359 | |
michael@0 | 2360 | // Table 3. |
michael@0 | 2361 | var properties = ["weekday", "era", "year", "month", "day", |
michael@0 | 2362 | "hour", "minute", "second", "timeZoneName"]; |
michael@0 | 2363 | |
michael@0 | 2364 | // Step 11.c.vi.1. |
michael@0 | 2365 | var values = ["2-digit", "numeric", "narrow", "short", "long"]; |
michael@0 | 2366 | |
michael@0 | 2367 | // Steps 7-8. |
michael@0 | 2368 | var bestScore = -Infinity; |
michael@0 | 2369 | var bestFormat; |
michael@0 | 2370 | |
michael@0 | 2371 | // Steps 9-11. |
michael@0 | 2372 | var i = 0; |
michael@0 | 2373 | var len = formats.length; |
michael@0 | 2374 | while (i < len) { |
michael@0 | 2375 | // Steps 11.a-b. |
michael@0 | 2376 | var format = formats[i]; |
michael@0 | 2377 | var score = 0; |
michael@0 | 2378 | |
michael@0 | 2379 | // Step 11.c. |
michael@0 | 2380 | var formatProp; |
michael@0 | 2381 | for (var j = 0; j < properties.length; j++) { |
michael@0 | 2382 | var property = properties[j]; |
michael@0 | 2383 | |
michael@0 | 2384 | // Step 11.c.i. |
michael@0 | 2385 | var optionsProp = options[property]; |
michael@0 | 2386 | // Step missing from spec. |
michael@0 | 2387 | // https://bugs.ecmascript.org/show_bug.cgi?id=1254 |
michael@0 | 2388 | formatProp = undefined; |
michael@0 | 2389 | |
michael@0 | 2390 | // Steps 11.c.ii-iii. |
michael@0 | 2391 | if (callFunction(std_Object_hasOwnProperty, format, property)) |
michael@0 | 2392 | formatProp = format[property]; |
michael@0 | 2393 | |
michael@0 | 2394 | if (optionsProp === undefined && formatProp !== undefined) { |
michael@0 | 2395 | // Step 11.c.iv. |
michael@0 | 2396 | score -= additionPenalty; |
michael@0 | 2397 | } else if (optionsProp !== undefined && formatProp === undefined) { |
michael@0 | 2398 | // Step 11.c.v. |
michael@0 | 2399 | score -= removalPenalty; |
michael@0 | 2400 | } else { |
michael@0 | 2401 | // Step 11.c.vi. |
michael@0 | 2402 | var optionsPropIndex = callFunction(std_Array_indexOf, values, optionsProp); |
michael@0 | 2403 | var formatPropIndex = callFunction(std_Array_indexOf, values, formatProp); |
michael@0 | 2404 | var delta = std_Math_max(std_Math_min(formatPropIndex - optionsPropIndex, 2), -2); |
michael@0 | 2405 | if (delta === 2) |
michael@0 | 2406 | score -= longMorePenalty; |
michael@0 | 2407 | else if (delta === 1) |
michael@0 | 2408 | score -= shortMorePenalty; |
michael@0 | 2409 | else if (delta === -1) |
michael@0 | 2410 | score -= shortLessPenalty; |
michael@0 | 2411 | else if (delta === -2) |
michael@0 | 2412 | score -= longLessPenalty; |
michael@0 | 2413 | } |
michael@0 | 2414 | } |
michael@0 | 2415 | |
michael@0 | 2416 | // Step 11.d. |
michael@0 | 2417 | if (score > bestScore) { |
michael@0 | 2418 | bestScore = score; |
michael@0 | 2419 | bestFormat = format; |
michael@0 | 2420 | } |
michael@0 | 2421 | |
michael@0 | 2422 | // Step 11.e. |
michael@0 | 2423 | i++; |
michael@0 | 2424 | } |
michael@0 | 2425 | |
michael@0 | 2426 | // Step 12. |
michael@0 | 2427 | return bestFormat; |
michael@0 | 2428 | } |
michael@0 | 2429 | |
michael@0 | 2430 | |
michael@0 | 2431 | /** |
michael@0 | 2432 | * Compares the date and time components requested by options with the available |
michael@0 | 2433 | * date and time formats in formats, and selects the best match according |
michael@0 | 2434 | * to an unspecified best-fit matching algorithm. |
michael@0 | 2435 | * |
michael@0 | 2436 | * Spec: ECMAScript Internationalization API Specification, 12.1.1. |
michael@0 | 2437 | */ |
michael@0 | 2438 | function BestFitFormatMatcher(options, formats) { |
michael@0 | 2439 | // this implementation doesn't have anything better |
michael@0 | 2440 | return BasicFormatMatcher(options, formats); |
michael@0 | 2441 | } |
michael@0 | 2442 | |
michael@0 | 2443 | |
michael@0 | 2444 | /** |
michael@0 | 2445 | * Returns the subset of the given locale list for which this locale list has a |
michael@0 | 2446 | * matching (possibly fallback) locale. Locales appear in the same order in the |
michael@0 | 2447 | * returned list as in the input list. |
michael@0 | 2448 | * |
michael@0 | 2449 | * Spec: ECMAScript Internationalization API Specification, 12.2.2. |
michael@0 | 2450 | */ |
michael@0 | 2451 | function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) { |
michael@0 | 2452 | var options = arguments.length > 1 ? arguments[1] : undefined; |
michael@0 | 2453 | |
michael@0 | 2454 | var availableLocales = dateTimeFormatInternalProperties.availableLocales(); |
michael@0 | 2455 | var requestedLocales = CanonicalizeLocaleList(locales); |
michael@0 | 2456 | return SupportedLocales(availableLocales, requestedLocales, options); |
michael@0 | 2457 | } |
michael@0 | 2458 | |
michael@0 | 2459 | |
michael@0 | 2460 | /** |
michael@0 | 2461 | * DateTimeFormat internal properties. |
michael@0 | 2462 | * |
michael@0 | 2463 | * Spec: ECMAScript Internationalization API Specification, 9.1 and 12.2.3. |
michael@0 | 2464 | */ |
michael@0 | 2465 | var dateTimeFormatInternalProperties = { |
michael@0 | 2466 | localeData: dateTimeFormatLocaleData, |
michael@0 | 2467 | _availableLocales: null, |
michael@0 | 2468 | availableLocales: function() |
michael@0 | 2469 | { |
michael@0 | 2470 | var locales = this._availableLocales; |
michael@0 | 2471 | if (locales) |
michael@0 | 2472 | return locales; |
michael@0 | 2473 | return (this._availableLocales = |
michael@0 | 2474 | addOldStyleLanguageTags(intl_DateTimeFormat_availableLocales())); |
michael@0 | 2475 | }, |
michael@0 | 2476 | relevantExtensionKeys: ["ca", "nu"] |
michael@0 | 2477 | }; |
michael@0 | 2478 | |
michael@0 | 2479 | |
michael@0 | 2480 | function dateTimeFormatLocaleData(locale) { |
michael@0 | 2481 | return { |
michael@0 | 2482 | ca: intl_availableCalendars(locale), |
michael@0 | 2483 | nu: getNumberingSystems(locale) |
michael@0 | 2484 | }; |
michael@0 | 2485 | } |
michael@0 | 2486 | |
michael@0 | 2487 | |
michael@0 | 2488 | /** |
michael@0 | 2489 | * Function to be bound and returned by Intl.DateTimeFormat.prototype.format. |
michael@0 | 2490 | * |
michael@0 | 2491 | * Spec: ECMAScript Internationalization API Specification, 12.3.2. |
michael@0 | 2492 | */ |
michael@0 | 2493 | function dateTimeFormatFormatToBind() { |
michael@0 | 2494 | // Steps 1.a.i-ii |
michael@0 | 2495 | var date = arguments.length > 0 ? arguments[0] : undefined; |
michael@0 | 2496 | var x = (date === undefined) ? std_Date_now() : ToNumber(date); |
michael@0 | 2497 | |
michael@0 | 2498 | // Step 1.a.iii. |
michael@0 | 2499 | return intl_FormatDateTime(this, x); |
michael@0 | 2500 | } |
michael@0 | 2501 | |
michael@0 | 2502 | |
michael@0 | 2503 | /** |
michael@0 | 2504 | * Returns a function bound to this DateTimeFormat that returns a String value |
michael@0 | 2505 | * representing the result of calling ToNumber(date) according to the |
michael@0 | 2506 | * effective locale and the formatting options of this DateTimeFormat. |
michael@0 | 2507 | * |
michael@0 | 2508 | * Spec: ECMAScript Internationalization API Specification, 12.3.2. |
michael@0 | 2509 | */ |
michael@0 | 2510 | function Intl_DateTimeFormat_format_get() { |
michael@0 | 2511 | // Check "this DateTimeFormat object" per introduction of section 12.3. |
michael@0 | 2512 | var internals = getDateTimeFormatInternals(this, "format"); |
michael@0 | 2513 | |
michael@0 | 2514 | // Step 1. |
michael@0 | 2515 | if (internals.boundFormat === undefined) { |
michael@0 | 2516 | // Step 1.a. |
michael@0 | 2517 | var F = dateTimeFormatFormatToBind; |
michael@0 | 2518 | |
michael@0 | 2519 | // Step 1.b-d. |
michael@0 | 2520 | var bf = callFunction(std_Function_bind, F, this); |
michael@0 | 2521 | internals.boundFormat = bf; |
michael@0 | 2522 | } |
michael@0 | 2523 | |
michael@0 | 2524 | // Step 2. |
michael@0 | 2525 | return internals.boundFormat; |
michael@0 | 2526 | } |
michael@0 | 2527 | |
michael@0 | 2528 | |
michael@0 | 2529 | /** |
michael@0 | 2530 | * Returns the resolved options for a DateTimeFormat object. |
michael@0 | 2531 | * |
michael@0 | 2532 | * Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4. |
michael@0 | 2533 | */ |
michael@0 | 2534 | function Intl_DateTimeFormat_resolvedOptions() { |
michael@0 | 2535 | // Check "this DateTimeFormat object" per introduction of section 12.3. |
michael@0 | 2536 | var internals = getDateTimeFormatInternals(this, "resolvedOptions"); |
michael@0 | 2537 | |
michael@0 | 2538 | var result = { |
michael@0 | 2539 | locale: internals.locale, |
michael@0 | 2540 | calendar: internals.calendar, |
michael@0 | 2541 | numberingSystem: internals.numberingSystem, |
michael@0 | 2542 | timeZone: internals.timeZone |
michael@0 | 2543 | }; |
michael@0 | 2544 | resolveICUPattern(internals.pattern, result); |
michael@0 | 2545 | return result; |
michael@0 | 2546 | } |
michael@0 | 2547 | |
michael@0 | 2548 | |
michael@0 | 2549 | // Table mapping ICU pattern characters back to the corresponding date-time |
michael@0 | 2550 | // components of DateTimeFormat. See |
michael@0 | 2551 | // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table |
michael@0 | 2552 | var icuPatternCharToComponent = { |
michael@0 | 2553 | E: "weekday", |
michael@0 | 2554 | G: "era", |
michael@0 | 2555 | y: "year", |
michael@0 | 2556 | M: "month", |
michael@0 | 2557 | L: "month", |
michael@0 | 2558 | d: "day", |
michael@0 | 2559 | h: "hour", |
michael@0 | 2560 | H: "hour", |
michael@0 | 2561 | k: "hour", |
michael@0 | 2562 | K: "hour", |
michael@0 | 2563 | m: "minute", |
michael@0 | 2564 | s: "second", |
michael@0 | 2565 | z: "timeZoneName", |
michael@0 | 2566 | v: "timeZoneName", |
michael@0 | 2567 | V: "timeZoneName" |
michael@0 | 2568 | }; |
michael@0 | 2569 | |
michael@0 | 2570 | |
michael@0 | 2571 | /** |
michael@0 | 2572 | * Maps an ICU pattern string to a corresponding set of date-time components |
michael@0 | 2573 | * and their values, and adds properties for these components to the result |
michael@0 | 2574 | * object, which will be returned by the resolvedOptions method. For the |
michael@0 | 2575 | * interpretation of ICU pattern characters, see |
michael@0 | 2576 | * http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table |
michael@0 | 2577 | */ |
michael@0 | 2578 | function resolveICUPattern(pattern, result) { |
michael@0 | 2579 | assert(IsObject(result), "resolveICUPattern"); |
michael@0 | 2580 | var i = 0; |
michael@0 | 2581 | while (i < pattern.length) { |
michael@0 | 2582 | var c = pattern[i++]; |
michael@0 | 2583 | if (c === "'") { |
michael@0 | 2584 | while (i < pattern.length && pattern[i] !== "'") |
michael@0 | 2585 | i++; |
michael@0 | 2586 | i++; |
michael@0 | 2587 | } else { |
michael@0 | 2588 | var count = 1; |
michael@0 | 2589 | while (i < pattern.length && pattern[i] === c) { |
michael@0 | 2590 | i++; |
michael@0 | 2591 | count++; |
michael@0 | 2592 | } |
michael@0 | 2593 | var value; |
michael@0 | 2594 | switch (c) { |
michael@0 | 2595 | // "text" cases |
michael@0 | 2596 | case "G": |
michael@0 | 2597 | case "E": |
michael@0 | 2598 | case "z": |
michael@0 | 2599 | case "v": |
michael@0 | 2600 | case "V": |
michael@0 | 2601 | if (count <= 3) |
michael@0 | 2602 | value = "short"; |
michael@0 | 2603 | else if (count === 4) |
michael@0 | 2604 | value = "long"; |
michael@0 | 2605 | else |
michael@0 | 2606 | value = "narrow"; |
michael@0 | 2607 | break; |
michael@0 | 2608 | // "number" cases |
michael@0 | 2609 | case "y": |
michael@0 | 2610 | case "d": |
michael@0 | 2611 | case "h": |
michael@0 | 2612 | case "H": |
michael@0 | 2613 | case "m": |
michael@0 | 2614 | case "s": |
michael@0 | 2615 | case "k": |
michael@0 | 2616 | case "K": |
michael@0 | 2617 | if (count === 2) |
michael@0 | 2618 | value = "2-digit"; |
michael@0 | 2619 | else |
michael@0 | 2620 | value = "numeric"; |
michael@0 | 2621 | break; |
michael@0 | 2622 | // "text & number" cases |
michael@0 | 2623 | case "M": |
michael@0 | 2624 | case "L": |
michael@0 | 2625 | if (count === 1) |
michael@0 | 2626 | value = "numeric"; |
michael@0 | 2627 | else if (count === 2) |
michael@0 | 2628 | value = "2-digit"; |
michael@0 | 2629 | else if (count === 3) |
michael@0 | 2630 | value = "short"; |
michael@0 | 2631 | else if (count === 4) |
michael@0 | 2632 | value = "long"; |
michael@0 | 2633 | else |
michael@0 | 2634 | value = "narrow"; |
michael@0 | 2635 | break; |
michael@0 | 2636 | default: |
michael@0 | 2637 | // skip other pattern characters and literal text |
michael@0 | 2638 | } |
michael@0 | 2639 | if (callFunction(std_Object_hasOwnProperty, icuPatternCharToComponent, c)) |
michael@0 | 2640 | defineProperty(result, icuPatternCharToComponent[c], value); |
michael@0 | 2641 | if (c === "h" || c === "K") |
michael@0 | 2642 | defineProperty(result, "hour12", true); |
michael@0 | 2643 | else if (c === "H" || c === "k") |
michael@0 | 2644 | defineProperty(result, "hour12", false); |
michael@0 | 2645 | } |
michael@0 | 2646 | } |
michael@0 | 2647 | } |