js/src/builtin/Intl.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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 }

mercurial