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