1 // Copyright 2013 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // ECMAScript 402 API implementation.
8 * Intl object is a single object that has some named properties,
9 * all of which are constructors.
11 (function(global, utils) {
15 %CheckIsBootstrapping();
17 // -------------------------------------------------------------------
20 var GlobalBoolean = global.Boolean;
21 var GlobalDate = global.Date;
22 var GlobalNumber = global.Number;
23 var GlobalRegExp = global.RegExp;
24 var GlobalString = global.String;
25 var ObjectDefineProperties = utils.ObjectDefineProperties;
26 var ObjectDefineProperty = utils.ObjectDefineProperty;
27 var SetFunctionName = utils.SetFunctionName;
36 var StringLastIndexOf;
43 utils.Import(function(from) {
44 ArrayIndexOf = from.ArrayIndexOf;
45 ArrayJoin = from.ArrayJoin;
46 IsFinite = from.IsFinite;
48 MathFloor = from.MathFloor;
49 RegExpTest = from.RegExpTest;
50 StringIndexOf = from.StringIndexOf;
51 StringLastIndexOf = from.StringLastIndexOf;
52 StringMatch = from.StringMatch;
53 StringReplace = from.StringReplace;
54 StringSplit = from.StringSplit;
55 StringSubstr = from.StringSubstr;
56 StringSubstring = from.StringSubstring;
59 // -------------------------------------------------------------------
63 %AddNamedProperty(global, "Intl", Intl, DONT_ENUM);
66 * Caches available locales for each service.
68 var AVAILABLE_LOCALES = {
69 'collator': UNDEFINED,
70 'numberformat': UNDEFINED,
71 'dateformat': UNDEFINED,
72 'breakiterator': UNDEFINED
76 * Caches default ICU locale.
78 var DEFAULT_ICU_LOCALE = UNDEFINED;
81 * Unicode extension regular expression.
83 var UNICODE_EXTENSION_RE = UNDEFINED;
85 function GetUnicodeExtensionRE() {
86 if (IS_UNDEFINED(UNDEFINED)) {
87 UNICODE_EXTENSION_RE = new GlobalRegExp('-u(-[a-z0-9]{2,8})+', 'g');
89 return UNICODE_EXTENSION_RE;
93 * Matches any Unicode extension.
95 var ANY_EXTENSION_RE = UNDEFINED;
97 function GetAnyExtensionRE() {
98 if (IS_UNDEFINED(ANY_EXTENSION_RE)) {
99 ANY_EXTENSION_RE = new GlobalRegExp('-[a-z0-9]{1}-.*', 'g');
101 return ANY_EXTENSION_RE;
105 * Replace quoted text (single quote, anything but the quote and quote again).
107 var QUOTED_STRING_RE = UNDEFINED;
109 function GetQuotedStringRE() {
110 if (IS_UNDEFINED(QUOTED_STRING_RE)) {
111 QUOTED_STRING_RE = new GlobalRegExp("'[^']+'", 'g');
113 return QUOTED_STRING_RE;
117 * Matches valid service name.
119 var SERVICE_RE = UNDEFINED;
121 function GetServiceRE() {
122 if (IS_UNDEFINED(SERVICE_RE)) {
124 new GlobalRegExp('^(collator|numberformat|dateformat|breakiterator)$');
130 * Validates a language tag against bcp47 spec.
131 * Actual value is assigned on first run.
133 var LANGUAGE_TAG_RE = UNDEFINED;
135 function GetLanguageTagRE() {
136 if (IS_UNDEFINED(LANGUAGE_TAG_RE)) {
137 BuildLanguageTagREs();
139 return LANGUAGE_TAG_RE;
143 * Helps find duplicate variants in the language tag.
145 var LANGUAGE_VARIANT_RE = UNDEFINED;
147 function GetLanguageVariantRE() {
148 if (IS_UNDEFINED(LANGUAGE_VARIANT_RE)) {
149 BuildLanguageTagREs();
151 return LANGUAGE_VARIANT_RE;
155 * Helps find duplicate singletons in the language tag.
157 var LANGUAGE_SINGLETON_RE = UNDEFINED;
159 function GetLanguageSingletonRE() {
160 if (IS_UNDEFINED(LANGUAGE_SINGLETON_RE)) {
161 BuildLanguageTagREs();
163 return LANGUAGE_SINGLETON_RE;
167 * Matches valid IANA time zone names.
169 var TIMEZONE_NAME_CHECK_RE = UNDEFINED;
171 function GetTimezoneNameCheckRE() {
172 if (IS_UNDEFINED(TIMEZONE_NAME_CHECK_RE)) {
173 TIMEZONE_NAME_CHECK_RE =
174 new GlobalRegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$');
176 return TIMEZONE_NAME_CHECK_RE;
180 * Adds bound method to the prototype of the given object.
182 function addBoundMethod(obj, methodName, implementation, length) {
183 %CheckIsBootstrapping();
185 if (!%IsInitializedIntlObject(this)) {
186 throw MakeTypeError(kMethodCalledOnWrongObject, methodName);
188 var internalName = '__bound' + methodName + '__';
189 if (IS_UNDEFINED(this[internalName])) {
192 if (IS_UNDEFINED(length) || length === 2) {
193 boundMethod = function(x, y) {
194 if (%_IsConstructCall()) {
195 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
197 return implementation(that, x, y);
199 } else if (length === 1) {
200 boundMethod = function(x) {
201 if (%_IsConstructCall()) {
202 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
204 return implementation(that, x);
207 boundMethod = function() {
208 if (%_IsConstructCall()) {
209 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
211 // DateTimeFormat.format needs to be 0 arg method, but can stil
212 // receive optional dateValue param. If one was provided, pass it
214 if (%_ArgumentsLength() > 0) {
215 return implementation(that, %_Arguments(0));
217 return implementation(that);
221 SetFunctionName(boundMethod, internalName);
222 %FunctionRemovePrototype(boundMethod);
223 %SetNativeFlag(boundMethod);
224 this[internalName] = boundMethod;
226 return this[internalName];
229 SetFunctionName(getter, methodName);
230 %FunctionRemovePrototype(getter);
231 %SetNativeFlag(getter);
233 ObjectDefineProperty(obj.prototype, methodName, {
242 * Returns an intersection of locales and service supported locales.
243 * Parameter locales is treated as a priority list.
245 function supportedLocalesOf(service, locales, options) {
246 if (IS_NULL(%_CallFunction(service, GetServiceRE(), StringMatch))) {
247 throw MakeError(kWrongServiceType, service);
250 // Provide defaults if matcher was not specified.
251 if (IS_UNDEFINED(options)) {
254 options = $toObject(options);
257 var matcher = options.localeMatcher;
258 if (!IS_UNDEFINED(matcher)) {
259 matcher = GlobalString(matcher);
260 if (matcher !== 'lookup' && matcher !== 'best fit') {
261 throw MakeRangeError(kLocaleMatcher, matcher);
264 matcher = 'best fit';
267 var requestedLocales = initializeLocaleList(locales);
269 // Cache these, they don't ever change per service.
270 if (IS_UNDEFINED(AVAILABLE_LOCALES[service])) {
271 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
274 // Use either best fit or lookup algorithm to match locales.
275 if (matcher === 'best fit') {
276 return initializeLocaleList(bestFitSupportedLocalesOf(
277 requestedLocales, AVAILABLE_LOCALES[service]));
280 return initializeLocaleList(lookupSupportedLocalesOf(
281 requestedLocales, AVAILABLE_LOCALES[service]));
286 * Returns the subset of the provided BCP 47 language priority list for which
287 * this service has a matching locale when using the BCP 47 Lookup algorithm.
288 * Locales appear in the same order in the returned list as in the input list.
290 function lookupSupportedLocalesOf(requestedLocales, availableLocales) {
291 var matchedLocales = [];
292 for (var i = 0; i < requestedLocales.length; ++i) {
293 // Remove -u- extension.
294 var locale = %_CallFunction(requestedLocales[i], GetUnicodeExtensionRE(),
297 if (!IS_UNDEFINED(availableLocales[locale])) {
298 // Push requested locale not the resolved one.
299 %_CallFunction(matchedLocales, requestedLocales[i], $arrayPush);
302 // Truncate locale if possible, if not break.
303 var pos = %_CallFunction(locale, '-', StringLastIndexOf);
307 locale = %_CallFunction(locale, 0, pos, StringSubstring);
311 return matchedLocales;
316 * Returns the subset of the provided BCP 47 language priority list for which
317 * this service has a matching locale when using the implementation
318 * dependent algorithm.
319 * Locales appear in the same order in the returned list as in the input list.
321 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) {
322 // TODO(cira): implement better best fit algorithm.
323 return lookupSupportedLocalesOf(requestedLocales, availableLocales);
328 * Returns a getOption function that extracts property value for given
329 * options object. If property is missing it returns defaultValue. If value
330 * is out of range for that property it throws RangeError.
332 function getGetOption(options, caller) {
333 if (IS_UNDEFINED(options)) throw MakeError(kDefaultOptionsMissing, caller);
335 var getOption = function getOption(property, type, values, defaultValue) {
336 if (!IS_UNDEFINED(options[property])) {
337 var value = options[property];
340 value = GlobalBoolean(value);
343 value = GlobalString(value);
346 value = GlobalNumber(value);
349 throw MakeError(kWrongValueType);
352 if (!IS_UNDEFINED(values) &&
353 %_CallFunction(values, value, ArrayIndexOf) === -1) {
354 throw MakeRangeError(kValueOutOfRange, value, caller, property);
368 * Compares a BCP 47 language priority list requestedLocales against the locales
369 * in availableLocales and determines the best available language to meet the
370 * request. Two algorithms are available to match the locales: the Lookup
371 * algorithm described in RFC 4647 section 3.4, and an implementation dependent
372 * best-fit algorithm. Independent of the locale matching algorithm, options
373 * specified through Unicode locale extension sequences are negotiated
374 * separately, taking the caller's relevant extension keys and locale data as
375 * well as client-provided options into consideration. Returns an object with
376 * a locale property whose value is the language tag of the selected locale,
377 * and properties for each key in relevantExtensionKeys providing the selected
378 * value for that key.
380 function resolveLocale(service, requestedLocales, options) {
381 requestedLocales = initializeLocaleList(requestedLocales);
383 var getOption = getGetOption(options, service);
384 var matcher = getOption('localeMatcher', 'string',
385 ['lookup', 'best fit'], 'best fit');
387 if (matcher === 'lookup') {
388 resolved = lookupMatcher(service, requestedLocales);
390 resolved = bestFitMatcher(service, requestedLocales);
398 * Returns best matched supported locale and extension info using basic
401 function lookupMatcher(service, requestedLocales) {
402 if (IS_NULL(%_CallFunction(service, GetServiceRE(), StringMatch))) {
403 throw MakeError(kWrongServiceType, service);
406 // Cache these, they don't ever change per service.
407 if (IS_UNDEFINED(AVAILABLE_LOCALES[service])) {
408 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
411 for (var i = 0; i < requestedLocales.length; ++i) {
412 // Remove all extensions.
413 var locale = %_CallFunction(requestedLocales[i], GetAnyExtensionRE(), '',
416 if (!IS_UNDEFINED(AVAILABLE_LOCALES[service][locale])) {
417 // Return the resolved locale and extension.
419 %_CallFunction(requestedLocales[i], GetUnicodeExtensionRE(),
421 var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0];
422 return {'locale': locale, 'extension': extension, 'position': i};
424 // Truncate locale if possible.
425 var pos = %_CallFunction(locale, '-', StringLastIndexOf);
429 locale = %_CallFunction(locale, 0, pos, StringSubstring);
433 // Didn't find a match, return default.
434 if (IS_UNDEFINED(DEFAULT_ICU_LOCALE)) {
435 DEFAULT_ICU_LOCALE = %GetDefaultICULocale();
438 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1};
443 * Returns best matched supported locale and extension info using
444 * implementation dependend algorithm.
446 function bestFitMatcher(service, requestedLocales) {
447 // TODO(cira): implement better best fit algorithm.
448 return lookupMatcher(service, requestedLocales);
453 * Parses Unicode extension into key - value map.
454 * Returns empty object if the extension string is invalid.
455 * We are not concerned with the validity of the values at this point.
457 function parseExtension(extension) {
458 var extensionSplit = %_CallFunction(extension, '-', StringSplit);
460 // Assume ['', 'u', ...] input, but don't throw.
461 if (extensionSplit.length <= 2 ||
462 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
466 // Key is {2}alphanum, value is {3,8}alphanum.
467 // Some keys may not have explicit values (booleans).
468 var extensionMap = {};
469 var previousKey = UNDEFINED;
470 for (var i = 2; i < extensionSplit.length; ++i) {
471 var length = extensionSplit[i].length;
472 var element = extensionSplit[i];
474 extensionMap[element] = UNDEFINED;
475 previousKey = element;
476 } else if (length >= 3 && length <=8 && !IS_UNDEFINED(previousKey)) {
477 extensionMap[previousKey] = element;
478 previousKey = UNDEFINED;
480 // There is a value that's too long, or that doesn't have a key.
490 * Populates internalOptions object with boolean key-value pairs
491 * from extensionMap and options.
492 * Returns filtered extension (number and date format constructors use
493 * Unicode extensions for passing parameters to ICU).
494 * It's used for extension-option pairs only, e.g. kn-normalization, but not
495 * for 'sensitivity' since it doesn't have extension equivalent.
496 * Extensions like nu and ca don't have options equivalent, so we place
497 * undefined in the map.property to denote that.
499 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
502 var updateExtension = function updateExtension(key, value) {
503 return '-' + key + '-' + GlobalString(value);
506 var updateProperty = function updateProperty(property, type, value) {
507 if (type === 'boolean' && (typeof value === 'string')) {
508 value = (value === 'true') ? true : false;
511 if (!IS_UNDEFINED(property)) {
512 defineWEProperty(outOptions, property, value);
516 for (var key in keyValues) {
517 if (%HasOwnProperty(keyValues, key)) {
518 var value = UNDEFINED;
519 var map = keyValues[key];
520 if (!IS_UNDEFINED(map.property)) {
521 // This may return true if user specifies numeric: 'false', since
522 // Boolean('nonempty') === true.
523 value = getOption(map.property, map.type, map.values);
525 if (!IS_UNDEFINED(value)) {
526 updateProperty(map.property, map.type, value);
527 extension += updateExtension(key, value);
530 // User options didn't have it, check Unicode extension.
531 // Here we want to convert strings 'true', 'false' into proper Boolean
532 // values (not a user error).
533 if (%HasOwnProperty(extensionMap, key)) {
534 value = extensionMap[key];
535 if (!IS_UNDEFINED(value)) {
536 updateProperty(map.property, map.type, value);
537 extension += updateExtension(key, value);
538 } else if (map.type === 'boolean') {
539 // Boolean keys are allowed not to have values in Unicode extension.
540 // Those default to true.
541 updateProperty(map.property, map.type, true);
542 extension += updateExtension(key, true);
548 return extension === ''? '' : '-u' + extension;
553 * Converts all OwnProperties into
554 * configurable: false, writable: false, enumerable: true.
556 function freezeArray(array) {
557 var l = array.length;
558 for (var i = 0; i < l; i++) {
560 ObjectDefineProperty(array, i, {value: array[i],
567 ObjectDefineProperty(array, 'length', {value: l, writable: false});
573 * It's sometimes desireable to leave user requested locale instead of ICU
574 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
575 * one, if that was what user requested).
576 * This function returns user specified tag if its maximized form matches ICU
577 * resolved locale. If not we return ICU result.
579 function getOptimalLanguageTag(original, resolved) {
580 // Returns Array<Object>, where each object has maximized and base properties.
581 // Maximized: zh -> zh-Hans-CN
582 // Base: zh-CN-u-ca-gregory -> zh-CN
583 // Take care of grandfathered or simple cases.
584 if (original === resolved) {
588 var locales = %GetLanguageTagVariants([original, resolved]);
589 if (locales[0].maximized !== locales[1].maximized) {
593 // Preserve extensions of resolved locale, but swap base tags with original.
594 var resolvedBase = new GlobalRegExp('^' + locales[1].base);
595 return %_CallFunction(resolved, resolvedBase, locales[0].base, StringReplace);
600 * Returns an Object that contains all of supported locales for a given
602 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ
603 * that is supported. This is required by the spec.
605 function getAvailableLocalesOf(service) {
606 var available = %AvailableLocalesOf(service);
608 for (var i in available) {
609 if (%HasOwnProperty(available, i)) {
610 var parts = %_CallFunction(i, /^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/,
612 if (parts !== null) {
613 // Build xx-ZZ. We don't care about the actual value,
614 // as long it's not undefined.
615 available[parts[1] + '-' + parts[3]] = null;
625 * Defines a property and sets writable and enumerable to true.
626 * Configurable is false by default.
628 function defineWEProperty(object, property, value) {
629 ObjectDefineProperty(object, property,
630 {value: value, writable: true, enumerable: true});
635 * Adds property to an object if the value is not undefined.
636 * Sets configurable descriptor to false.
638 function addWEPropertyIfDefined(object, property, value) {
639 if (!IS_UNDEFINED(value)) {
640 defineWEProperty(object, property, value);
646 * Defines a property and sets writable, enumerable and configurable to true.
648 function defineWECProperty(object, property, value) {
649 ObjectDefineProperty(object, property, {value: value,
652 configurable: true});
657 * Adds property to an object if the value is not undefined.
658 * Sets all descriptors to true.
660 function addWECPropertyIfDefined(object, property, value) {
661 if (!IS_UNDEFINED(value)) {
662 defineWECProperty(object, property, value);
668 * Returns titlecased word, aMeRricA -> America.
670 function toTitleCaseWord(word) {
671 return %StringToUpperCase(%_CallFunction(word, 0, 1, StringSubstr)) +
672 %StringToLowerCase(%_CallFunction(word, 1, StringSubstr));
676 * Canonicalizes the language tag, or throws in case the tag is invalid.
678 function canonicalizeLanguageTag(localeID) {
679 // null is typeof 'object' so we have to do extra check.
680 if (typeof localeID !== 'string' && typeof localeID !== 'object' ||
682 throw MakeTypeError(kLanguageID);
685 var localeString = GlobalString(localeID);
687 if (isValidLanguageTag(localeString) === false) {
688 throw MakeRangeError(kInvalidLanguageTag, localeString);
691 // This call will strip -kn but not -kn-true extensions.
692 // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265.
693 // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after
694 // upgrade to ICU 4.9.
695 var tag = %CanonicalizeLanguageTag(localeString);
696 if (tag === 'invalid-tag') {
697 throw MakeRangeError(kInvalidLanguageTag, localeString);
705 * Returns an array where all locales are canonicalized and duplicates removed.
706 * Throws on locales that are not well formed BCP47 tags.
708 function initializeLocaleList(locales) {
710 if (IS_UNDEFINED(locales)) {
711 // Constructor is called without arguments.
714 // We allow single string localeID.
715 if (typeof locales === 'string') {
716 %_CallFunction(seen, canonicalizeLanguageTag(locales), $arrayPush);
717 return freezeArray(seen);
720 var o = $toObject(locales);
721 var len = TO_UINT32(o.length);
723 for (var k = 0; k < len; k++) {
727 var tag = canonicalizeLanguageTag(value);
729 if (%_CallFunction(seen, tag, ArrayIndexOf) === -1) {
730 %_CallFunction(seen, tag, $arrayPush);
736 return freezeArray(seen);
741 * Validates the language tag. Section 2.2.9 of the bcp47 spec
742 * defines a valid tag.
744 * ICU is too permissible and lets invalid tags, like
745 * hant-cmn-cn, through.
747 * Returns false if the language tag is invalid.
749 function isValidLanguageTag(locale) {
750 // Check if it's well-formed, including grandfadered tags.
751 if (!%_CallFunction(GetLanguageTagRE(), locale, RegExpTest)) {
755 // Just return if it's a x- form. It's all private.
756 if (%_CallFunction(locale, 'x-', StringIndexOf) === 0) {
760 // Check if there are any duplicate variants or singletons (extensions).
762 // Remove private use section.
763 locale = %_CallFunction(locale, /-x-/, StringSplit)[0];
765 // Skip language since it can match variant regex, so we start from 1.
766 // We are matching i-klingon here, but that's ok, since i-klingon-klingon
767 // is not valid and would fail LANGUAGE_TAG_RE test.
770 var parts = %_CallFunction(locale, /-/, StringSplit);
771 for (var i = 1; i < parts.length; i++) {
772 var value = parts[i];
773 if (%_CallFunction(GetLanguageVariantRE(), value, RegExpTest) &&
774 extensions.length === 0) {
775 if (%_CallFunction(variants, value, ArrayIndexOf) === -1) {
776 %_CallFunction(variants, value, $arrayPush);
782 if (%_CallFunction(GetLanguageSingletonRE(), value, RegExpTest)) {
783 if (%_CallFunction(extensions, value, ArrayIndexOf) === -1) {
784 %_CallFunction(extensions, value, $arrayPush);
796 * Builds a regular expresion that validates the language tag
797 * against bcp47 spec.
798 * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF.
799 * Runs on load and initializes the global REs.
801 function BuildLanguageTagREs() {
802 var alpha = '[a-zA-Z]';
804 var alphanum = '(' + alpha + '|' + digit + ')';
805 var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' +
806 'zh-min|zh-min-nan|zh-xiang)';
807 var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' +
808 'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' +
809 'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)';
810 var grandfathered = '(' + irregular + '|' + regular + ')';
811 var privateUse = '(x(-' + alphanum + '{1,8})+)';
813 var singleton = '(' + digit + '|[A-WY-Za-wy-z])';
814 LANGUAGE_SINGLETON_RE = new GlobalRegExp('^' + singleton + '$', 'i');
816 var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)';
818 var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))';
819 LANGUAGE_VARIANT_RE = new GlobalRegExp('^' + variant + '$', 'i');
821 var region = '(' + alpha + '{2}|' + digit + '{3})';
822 var script = '(' + alpha + '{4})';
823 var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})';
824 var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' +
826 var langTag = language + '(-' + script + ')?(-' + region + ')?(-' +
827 variant + ')*(-' + extension + ')*(-' + privateUse + ')?';
830 '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$';
831 LANGUAGE_TAG_RE = new GlobalRegExp(languageTag, 'i');
835 * Initializes the given object so it's a valid Collator instance.
836 * Useful for subclassing.
838 function initializeCollator(collator, locales, options) {
839 if (%IsInitializedIntlObject(collator)) {
840 throw MakeTypeError(kReinitializeIntl, "Collator");
843 if (IS_UNDEFINED(options)) {
847 var getOption = getGetOption(options, 'collator');
849 var internalOptions = {};
851 defineWEProperty(internalOptions, 'usage', getOption(
852 'usage', 'string', ['sort', 'search'], 'sort'));
854 var sensitivity = getOption('sensitivity', 'string',
855 ['base', 'accent', 'case', 'variant']);
856 if (IS_UNDEFINED(sensitivity) && internalOptions.usage === 'sort') {
857 sensitivity = 'variant';
859 defineWEProperty(internalOptions, 'sensitivity', sensitivity);
861 defineWEProperty(internalOptions, 'ignorePunctuation', getOption(
862 'ignorePunctuation', 'boolean', UNDEFINED, false));
864 var locale = resolveLocale('collator', locales, options);
866 // ICU can't take kb, kc... parameters through localeID, so we need to pass
868 // One exception is -co- which has to be part of the extension, but only for
869 // usage: sort, and its value can't be 'standard' or 'search'.
870 var extensionMap = parseExtension(locale.extension);
873 * Map of Unicode extensions to option properties, and their values and types,
876 var COLLATOR_KEY_MAP = {
877 'kn': {'property': 'numeric', 'type': 'boolean'},
878 'kf': {'property': 'caseFirst', 'type': 'string',
879 'values': ['false', 'lower', 'upper']}
883 options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions);
885 var collation = 'default';
887 if (%HasOwnProperty(extensionMap, 'co') && internalOptions.usage === 'sort') {
890 * Allowed -u-co- values. List taken from:
891 * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml
893 var ALLOWED_CO_VALUES = [
894 'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic',
895 'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin'
898 if (%_CallFunction(ALLOWED_CO_VALUES, extensionMap.co, ArrayIndexOf) !==
900 extension = '-u-co-' + extensionMap.co;
901 // ICU can't tell us what the collation is, so save user's input.
902 collation = extensionMap.co;
904 } else if (internalOptions.usage === 'search') {
905 extension = '-u-co-search';
907 defineWEProperty(internalOptions, 'collation', collation);
909 var requestedLocale = locale.locale + extension;
911 // We define all properties C++ code may produce, to prevent security
912 // problems. If malicious user decides to redefine Object.prototype.locale
913 // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us").
914 // ObjectDefineProperties will either succeed defining or throw an error.
915 var resolved = ObjectDefineProperties({}, {
916 caseFirst: {writable: true},
917 collation: {value: internalOptions.collation, writable: true},
918 ignorePunctuation: {writable: true},
919 locale: {writable: true},
920 numeric: {writable: true},
921 requestedLocale: {value: requestedLocale, writable: true},
922 sensitivity: {writable: true},
923 strength: {writable: true},
924 usage: {value: internalOptions.usage, writable: true}
927 var internalCollator = %CreateCollator(requestedLocale,
931 // Writable, configurable and enumerable are set to false by default.
932 %MarkAsInitializedIntlObjectOfType(collator, 'collator', internalCollator);
933 ObjectDefineProperty(collator, 'resolved', {value: resolved});
940 * Constructs Intl.Collator object given optional locales and options
945 %AddNamedProperty(Intl, 'Collator', function() {
946 var locales = %_Arguments(0);
947 var options = %_Arguments(1);
949 if (!this || this === Intl) {
950 // Constructor is called as a function.
951 return new Intl.Collator(locales, options);
954 return initializeCollator($toObject(this), locales, options);
961 * Collator resolvedOptions method.
963 %AddNamedProperty(Intl.Collator.prototype, 'resolvedOptions', function() {
964 if (%_IsConstructCall()) {
965 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
968 if (!%IsInitializedIntlObjectOfType(this, 'collator')) {
969 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "Collator");
973 var locale = getOptimalLanguageTag(coll.resolved.requestedLocale,
974 coll.resolved.locale);
978 usage: coll.resolved.usage,
979 sensitivity: coll.resolved.sensitivity,
980 ignorePunctuation: coll.resolved.ignorePunctuation,
981 numeric: coll.resolved.numeric,
982 caseFirst: coll.resolved.caseFirst,
983 collation: coll.resolved.collation
988 SetFunctionName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions');
989 %FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions);
990 %SetNativeFlag(Intl.Collator.prototype.resolvedOptions);
994 * Returns the subset of the given locale list for which this locale list
995 * has a matching (possibly fallback) locale. Locales appear in the same
996 * order in the returned list as in the input list.
997 * Options are optional parameter.
999 %AddNamedProperty(Intl.Collator, 'supportedLocalesOf', function(locales) {
1000 if (%_IsConstructCall()) {
1001 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1004 return supportedLocalesOf('collator', locales, %_Arguments(1));
1008 SetFunctionName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf');
1009 %FunctionRemovePrototype(Intl.Collator.supportedLocalesOf);
1010 %SetNativeFlag(Intl.Collator.supportedLocalesOf);
1014 * When the compare method is called with two arguments x and y, it returns a
1015 * Number other than NaN that represents the result of a locale-sensitive
1016 * String comparison of x with y.
1017 * The result is intended to order String values in the sort order specified
1018 * by the effective locale and collation options computed during construction
1019 * of this Collator object, and will be negative, zero, or positive, depending
1020 * on whether x comes before y in the sort order, the Strings are equal under
1021 * the sort order, or x comes after y in the sort order, respectively.
1023 function compare(collator, x, y) {
1024 return %InternalCompare(%GetImplFromInitializedIntlObject(collator),
1025 GlobalString(x), GlobalString(y));
1029 addBoundMethod(Intl.Collator, 'compare', compare, 2);
1032 * Verifies that the input is a well-formed ISO 4217 currency code.
1033 * Don't uppercase to test. It could convert invalid code into a valid one.
1034 * For example \u00DFP (Eszett+P) becomes SSP.
1036 function isWellFormedCurrencyCode(currency) {
1037 return typeof currency == "string" &&
1038 currency.length == 3 &&
1039 %_CallFunction(currency, /[^A-Za-z]/, StringMatch) == null;
1044 * Returns the valid digit count for a property, or throws RangeError on
1045 * a value out of the range.
1047 function getNumberOption(options, property, min, max, fallback) {
1048 var value = options[property];
1049 if (!IS_UNDEFINED(value)) {
1050 value = GlobalNumber(value);
1051 if (IsNaN(value) || value < min || value > max) {
1052 throw MakeRangeError(kPropertyValueOutOfRange, property);
1054 return MathFloor(value);
1062 * Initializes the given object so it's a valid NumberFormat instance.
1063 * Useful for subclassing.
1065 function initializeNumberFormat(numberFormat, locales, options) {
1066 if (%IsInitializedIntlObject(numberFormat)) {
1067 throw MakeTypeError(kReinitializeIntl, "NumberFormat");
1070 if (IS_UNDEFINED(options)) {
1074 var getOption = getGetOption(options, 'numberformat');
1076 var locale = resolveLocale('numberformat', locales, options);
1078 var internalOptions = {};
1079 defineWEProperty(internalOptions, 'style', getOption(
1080 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal'));
1082 var currency = getOption('currency', 'string');
1083 if (!IS_UNDEFINED(currency) && !isWellFormedCurrencyCode(currency)) {
1084 throw MakeRangeError(kInvalidCurrencyCode, currency);
1087 if (internalOptions.style === 'currency' && IS_UNDEFINED(currency)) {
1088 throw MakeTypeError(kCurrencyCode);
1091 var currencyDisplay = getOption(
1092 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
1093 if (internalOptions.style === 'currency') {
1094 defineWEProperty(internalOptions, 'currency', %StringToUpperCase(currency));
1095 defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay);
1099 var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
1100 defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid);
1102 var mnfd = options['minimumFractionDigits'];
1103 var mxfd = options['maximumFractionDigits'];
1104 if (!IS_UNDEFINED(mnfd) || !internalOptions.style === 'currency') {
1105 mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0);
1106 defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd);
1109 if (!IS_UNDEFINED(mxfd) || !internalOptions.style === 'currency') {
1110 mnfd = IS_UNDEFINED(mnfd) ? 0 : mnfd;
1111 fallback_limit = (mnfd > 3) ? mnfd : 3;
1112 mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, fallback_limit);
1113 defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd);
1116 var mnsd = options['minimumSignificantDigits'];
1117 var mxsd = options['maximumSignificantDigits'];
1118 if (!IS_UNDEFINED(mnsd) || !IS_UNDEFINED(mxsd)) {
1119 mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0);
1120 defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd);
1122 mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21);
1123 defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd);
1127 defineWEProperty(internalOptions, 'useGrouping', getOption(
1128 'useGrouping', 'boolean', UNDEFINED, true));
1130 // ICU prefers options to be passed using -u- extension key/values for
1131 // number format, so we need to build that.
1132 var extensionMap = parseExtension(locale.extension);
1135 * Map of Unicode extensions to option properties, and their values and types,
1136 * for a number format.
1138 var NUMBER_FORMAT_KEY_MAP = {
1139 'nu': {'property': UNDEFINED, 'type': 'string'}
1142 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP,
1143 getOption, internalOptions);
1145 var requestedLocale = locale.locale + extension;
1146 var resolved = ObjectDefineProperties({}, {
1147 currency: {writable: true},
1148 currencyDisplay: {writable: true},
1149 locale: {writable: true},
1150 maximumFractionDigits: {writable: true},
1151 minimumFractionDigits: {writable: true},
1152 minimumIntegerDigits: {writable: true},
1153 numberingSystem: {writable: true},
1154 requestedLocale: {value: requestedLocale, writable: true},
1155 style: {value: internalOptions.style, writable: true},
1156 useGrouping: {writable: true}
1158 if (%HasOwnProperty(internalOptions, 'minimumSignificantDigits')) {
1159 defineWEProperty(resolved, 'minimumSignificantDigits', UNDEFINED);
1161 if (%HasOwnProperty(internalOptions, 'maximumSignificantDigits')) {
1162 defineWEProperty(resolved, 'maximumSignificantDigits', UNDEFINED);
1164 var formatter = %CreateNumberFormat(requestedLocale,
1168 if (internalOptions.style === 'currency') {
1169 ObjectDefineProperty(resolved, 'currencyDisplay', {value: currencyDisplay,
1173 %MarkAsInitializedIntlObjectOfType(numberFormat, 'numberformat', formatter);
1174 ObjectDefineProperty(numberFormat, 'resolved', {value: resolved});
1176 return numberFormat;
1181 * Constructs Intl.NumberFormat object given optional locales and options
1186 %AddNamedProperty(Intl, 'NumberFormat', function() {
1187 var locales = %_Arguments(0);
1188 var options = %_Arguments(1);
1190 if (!this || this === Intl) {
1191 // Constructor is called as a function.
1192 return new Intl.NumberFormat(locales, options);
1195 return initializeNumberFormat($toObject(this), locales, options);
1202 * NumberFormat resolvedOptions method.
1204 %AddNamedProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() {
1205 if (%_IsConstructCall()) {
1206 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1209 if (!%IsInitializedIntlObjectOfType(this, 'numberformat')) {
1210 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "NumberFormat");
1214 var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1215 format.resolved.locale);
1219 numberingSystem: format.resolved.numberingSystem,
1220 style: format.resolved.style,
1221 useGrouping: format.resolved.useGrouping,
1222 minimumIntegerDigits: format.resolved.minimumIntegerDigits,
1223 minimumFractionDigits: format.resolved.minimumFractionDigits,
1224 maximumFractionDigits: format.resolved.maximumFractionDigits,
1227 if (result.style === 'currency') {
1228 defineWECProperty(result, 'currency', format.resolved.currency);
1229 defineWECProperty(result, 'currencyDisplay',
1230 format.resolved.currencyDisplay);
1233 if (%HasOwnProperty(format.resolved, 'minimumSignificantDigits')) {
1234 defineWECProperty(result, 'minimumSignificantDigits',
1235 format.resolved.minimumSignificantDigits);
1238 if (%HasOwnProperty(format.resolved, 'maximumSignificantDigits')) {
1239 defineWECProperty(result, 'maximumSignificantDigits',
1240 format.resolved.maximumSignificantDigits);
1247 SetFunctionName(Intl.NumberFormat.prototype.resolvedOptions, 'resolvedOptions');
1248 %FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions);
1249 %SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions);
1253 * Returns the subset of the given locale list for which this locale list
1254 * has a matching (possibly fallback) locale. Locales appear in the same
1255 * order in the returned list as in the input list.
1256 * Options are optional parameter.
1258 %AddNamedProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) {
1259 if (%_IsConstructCall()) {
1260 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1263 return supportedLocalesOf('numberformat', locales, %_Arguments(1));
1267 SetFunctionName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf');
1268 %FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf);
1269 %SetNativeFlag(Intl.NumberFormat.supportedLocalesOf);
1273 * Returns a String value representing the result of calling ToNumber(value)
1274 * according to the effective locale and the formatting options of this
1277 function formatNumber(formatter, value) {
1278 // Spec treats -0 and +0 as 0.
1279 var number = $toNumber(value) + 0;
1281 return %InternalNumberFormat(%GetImplFromInitializedIntlObject(formatter),
1287 * Returns a Number that represents string value that was passed in.
1289 function parseNumber(formatter, value) {
1290 return %InternalNumberParse(%GetImplFromInitializedIntlObject(formatter),
1291 GlobalString(value));
1295 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1);
1296 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1);
1299 * Returns a string that matches LDML representation of the options object.
1301 function toLDMLString(options) {
1302 var getOption = getGetOption(options, 'dateformat');
1304 var ldmlString = '';
1306 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']);
1307 ldmlString += appendToLDMLString(
1308 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'});
1310 option = getOption('era', 'string', ['narrow', 'short', 'long']);
1311 ldmlString += appendToLDMLString(
1312 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'});
1314 option = getOption('year', 'string', ['2-digit', 'numeric']);
1315 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'});
1317 option = getOption('month', 'string',
1318 ['2-digit', 'numeric', 'narrow', 'short', 'long']);
1319 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M',
1320 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'});
1322 option = getOption('day', 'string', ['2-digit', 'numeric']);
1323 ldmlString += appendToLDMLString(
1324 option, {'2-digit': 'dd', 'numeric': 'd'});
1326 var hr12 = getOption('hour12', 'boolean');
1327 option = getOption('hour', 'string', ['2-digit', 'numeric']);
1328 if (IS_UNDEFINED(hr12)) {
1329 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'});
1330 } else if (hr12 === true) {
1331 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'});
1333 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'});
1336 option = getOption('minute', 'string', ['2-digit', 'numeric']);
1337 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'});
1339 option = getOption('second', 'string', ['2-digit', 'numeric']);
1340 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'});
1342 option = getOption('timeZoneName', 'string', ['short', 'long']);
1343 ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'});
1350 * Returns either LDML equivalent of the current option or empty string.
1352 function appendToLDMLString(option, pairs) {
1353 if (!IS_UNDEFINED(option)) {
1354 return pairs[option];
1362 * Returns object that matches LDML representation of the date.
1364 function fromLDMLString(ldmlString) {
1365 // First remove '' quoted text, so we lose 'Uhr' strings.
1366 ldmlString = %_CallFunction(ldmlString, GetQuotedStringRE(), '',
1370 var match = %_CallFunction(ldmlString, /E{3,5}/g, StringMatch);
1371 options = appendToDateTimeObject(
1372 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'});
1374 match = %_CallFunction(ldmlString, /G{3,5}/g, StringMatch);
1375 options = appendToDateTimeObject(
1376 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'});
1378 match = %_CallFunction(ldmlString, /y{1,2}/g, StringMatch);
1379 options = appendToDateTimeObject(
1380 options, 'year', match, {y: 'numeric', yy: '2-digit'});
1382 match = %_CallFunction(ldmlString, /M{1,5}/g, StringMatch);
1383 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit',
1384 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'});
1386 // Sometimes we get L instead of M for month - standalone name.
1387 match = %_CallFunction(ldmlString, /L{1,5}/g, StringMatch);
1388 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit',
1389 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'});
1391 match = %_CallFunction(ldmlString, /d{1,2}/g, StringMatch);
1392 options = appendToDateTimeObject(
1393 options, 'day', match, {d: 'numeric', dd: '2-digit'});
1395 match = %_CallFunction(ldmlString, /h{1,2}/g, StringMatch);
1396 if (match !== null) {
1397 options['hour12'] = true;
1399 options = appendToDateTimeObject(
1400 options, 'hour', match, {h: 'numeric', hh: '2-digit'});
1402 match = %_CallFunction(ldmlString, /H{1,2}/g, StringMatch);
1403 if (match !== null) {
1404 options['hour12'] = false;
1406 options = appendToDateTimeObject(
1407 options, 'hour', match, {H: 'numeric', HH: '2-digit'});
1409 match = %_CallFunction(ldmlString, /m{1,2}/g, StringMatch);
1410 options = appendToDateTimeObject(
1411 options, 'minute', match, {m: 'numeric', mm: '2-digit'});
1413 match = %_CallFunction(ldmlString, /s{1,2}/g, StringMatch);
1414 options = appendToDateTimeObject(
1415 options, 'second', match, {s: 'numeric', ss: '2-digit'});
1417 match = %_CallFunction(ldmlString, /z|zzzz/g, StringMatch);
1418 options = appendToDateTimeObject(
1419 options, 'timeZoneName', match, {z: 'short', zzzz: 'long'});
1425 function appendToDateTimeObject(options, option, match, pairs) {
1426 if (IS_NULL(match)) {
1427 if (!%HasOwnProperty(options, option)) {
1428 defineWEProperty(options, option, UNDEFINED);
1433 var property = match[0];
1434 defineWEProperty(options, option, pairs[property]);
1441 * Returns options with at least default values in it.
1443 function toDateTimeOptions(options, required, defaults) {
1444 if (IS_UNDEFINED(options)) {
1447 options = TO_OBJECT_INLINE(options);
1450 var needsDefault = true;
1451 if ((required === 'date' || required === 'any') &&
1452 (!IS_UNDEFINED(options.weekday) || !IS_UNDEFINED(options.year) ||
1453 !IS_UNDEFINED(options.month) || !IS_UNDEFINED(options.day))) {
1454 needsDefault = false;
1457 if ((required === 'time' || required === 'any') &&
1458 (!IS_UNDEFINED(options.hour) || !IS_UNDEFINED(options.minute) ||
1459 !IS_UNDEFINED(options.second))) {
1460 needsDefault = false;
1463 if (needsDefault && (defaults === 'date' || defaults === 'all')) {
1464 ObjectDefineProperty(options, 'year', {value: 'numeric',
1467 configurable: true});
1468 ObjectDefineProperty(options, 'month', {value: 'numeric',
1471 configurable: true});
1472 ObjectDefineProperty(options, 'day', {value: 'numeric',
1475 configurable: true});
1478 if (needsDefault && (defaults === 'time' || defaults === 'all')) {
1479 ObjectDefineProperty(options, 'hour', {value: 'numeric',
1482 configurable: true});
1483 ObjectDefineProperty(options, 'minute', {value: 'numeric',
1486 configurable: true});
1487 ObjectDefineProperty(options, 'second', {value: 'numeric',
1490 configurable: true});
1498 * Initializes the given object so it's a valid DateTimeFormat instance.
1499 * Useful for subclassing.
1501 function initializeDateTimeFormat(dateFormat, locales, options) {
1503 if (%IsInitializedIntlObject(dateFormat)) {
1504 throw MakeTypeError(kReinitializeIntl, "DateTimeFormat");
1507 if (IS_UNDEFINED(options)) {
1511 var locale = resolveLocale('dateformat', locales, options);
1513 options = toDateTimeOptions(options, 'any', 'date');
1515 var getOption = getGetOption(options, 'dateformat');
1517 // We implement only best fit algorithm, but still need to check
1518 // if the formatMatcher values are in range.
1519 var matcher = getOption('formatMatcher', 'string',
1520 ['basic', 'best fit'], 'best fit');
1522 // Build LDML string for the skeleton that we pass to the formatter.
1523 var ldmlString = toLDMLString(options);
1525 // Filter out supported extension keys so we know what to put in resolved
1526 // section later on.
1527 // We need to pass calendar and number system to the method.
1528 var tz = canonicalizeTimeZoneID(options.timeZone);
1530 // ICU prefers options to be passed using -u- extension key/values, so
1531 // we need to build that.
1532 var internalOptions = {};
1533 var extensionMap = parseExtension(locale.extension);
1536 * Map of Unicode extensions to option properties, and their values and types,
1537 * for a date/time format.
1539 var DATETIME_FORMAT_KEY_MAP = {
1540 'ca': {'property': UNDEFINED, 'type': 'string'},
1541 'nu': {'property': UNDEFINED, 'type': 'string'}
1544 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP,
1545 getOption, internalOptions);
1547 var requestedLocale = locale.locale + extension;
1548 var resolved = ObjectDefineProperties({}, {
1549 calendar: {writable: true},
1550 day: {writable: true},
1551 era: {writable: true},
1552 hour12: {writable: true},
1553 hour: {writable: true},
1554 locale: {writable: true},
1555 minute: {writable: true},
1556 month: {writable: true},
1557 numberingSystem: {writable: true},
1558 pattern: {writable: true},
1559 requestedLocale: {value: requestedLocale, writable: true},
1560 second: {writable: true},
1561 timeZone: {writable: true},
1562 timeZoneName: {writable: true},
1563 tz: {value: tz, writable: true},
1564 weekday: {writable: true},
1565 year: {writable: true}
1568 var formatter = %CreateDateTimeFormat(
1569 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved);
1571 if (!IS_UNDEFINED(tz) && tz !== resolved.timeZone) {
1572 throw MakeRangeError(kUnsupportedTimeZone, tz);
1575 %MarkAsInitializedIntlObjectOfType(dateFormat, 'dateformat', formatter);
1576 ObjectDefineProperty(dateFormat, 'resolved', {value: resolved});
1583 * Constructs Intl.DateTimeFormat object given optional locales and options
1588 %AddNamedProperty(Intl, 'DateTimeFormat', function() {
1589 var locales = %_Arguments(0);
1590 var options = %_Arguments(1);
1592 if (!this || this === Intl) {
1593 // Constructor is called as a function.
1594 return new Intl.DateTimeFormat(locales, options);
1597 return initializeDateTimeFormat($toObject(this), locales, options);
1604 * DateTimeFormat resolvedOptions method.
1606 %AddNamedProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
1607 if (%_IsConstructCall()) {
1608 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1611 if (!%IsInitializedIntlObjectOfType(this, 'dateformat')) {
1612 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "DateTimeFormat");
1616 * Maps ICU calendar names into LDML type.
1618 var ICU_CALENDAR_MAP = {
1619 'gregorian': 'gregory',
1620 'japanese': 'japanese',
1621 'buddhist': 'buddhist',
1623 'persian': 'persian',
1624 'islamic-civil': 'islamicc',
1625 'islamic': 'islamic',
1627 'chinese': 'chinese',
1630 'ethiopic': 'ethiopic',
1631 'ethiopic-amete-alem': 'ethioaa'
1635 var fromPattern = fromLDMLString(format.resolved.pattern);
1636 var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar];
1637 if (IS_UNDEFINED(userCalendar)) {
1638 // Use ICU name if we don't have a match. It shouldn't happen, but
1639 // it would be too strict to throw for this.
1640 userCalendar = format.resolved.calendar;
1643 var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1644 format.resolved.locale);
1648 numberingSystem: format.resolved.numberingSystem,
1649 calendar: userCalendar,
1650 timeZone: format.resolved.timeZone
1653 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName);
1654 addWECPropertyIfDefined(result, 'era', fromPattern.era);
1655 addWECPropertyIfDefined(result, 'year', fromPattern.year);
1656 addWECPropertyIfDefined(result, 'month', fromPattern.month);
1657 addWECPropertyIfDefined(result, 'day', fromPattern.day);
1658 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday);
1659 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12);
1660 addWECPropertyIfDefined(result, 'hour', fromPattern.hour);
1661 addWECPropertyIfDefined(result, 'minute', fromPattern.minute);
1662 addWECPropertyIfDefined(result, 'second', fromPattern.second);
1668 SetFunctionName(Intl.DateTimeFormat.prototype.resolvedOptions,
1670 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions);
1671 %SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions);
1675 * Returns the subset of the given locale list for which this locale list
1676 * has a matching (possibly fallback) locale. Locales appear in the same
1677 * order in the returned list as in the input list.
1678 * Options are optional parameter.
1680 %AddNamedProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) {
1681 if (%_IsConstructCall()) {
1682 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1685 return supportedLocalesOf('dateformat', locales, %_Arguments(1));
1689 SetFunctionName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf');
1690 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf);
1691 %SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf);
1695 * Returns a String value representing the result of calling ToNumber(date)
1696 * according to the effective locale and the formatting options of this
1699 function formatDate(formatter, dateValue) {
1701 if (IS_UNDEFINED(dateValue)) {
1702 dateMs = %DateCurrentTime();
1704 dateMs = $toNumber(dateValue);
1707 if (!IsFinite(dateMs)) throw MakeRangeError(kDateRange);
1709 return %InternalDateFormat(%GetImplFromInitializedIntlObject(formatter),
1710 new GlobalDate(dateMs));
1715 * Returns a Date object representing the result of calling ToString(value)
1716 * according to the effective locale and the formatting options of this
1718 * Returns undefined if date string cannot be parsed.
1720 function parseDate(formatter, value) {
1721 return %InternalDateParse(%GetImplFromInitializedIntlObject(formatter),
1722 GlobalString(value));
1726 // 0 because date is optional argument.
1727 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0);
1728 addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1);
1732 * Returns canonical Area/Location name, or throws an exception if the zone
1733 * name is invalid IANA name.
1735 function canonicalizeTimeZoneID(tzID) {
1736 // Skip undefined zones.
1737 if (IS_UNDEFINED(tzID)) {
1741 // Special case handling (UTC, GMT).
1742 var upperID = %StringToUpperCase(tzID);
1743 if (upperID === 'UTC' || upperID === 'GMT' ||
1744 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') {
1748 // We expect only _ and / beside ASCII letters.
1749 // All inputs should conform to Area/Location from now on.
1750 var match = %_CallFunction(tzID, GetTimezoneNameCheckRE(), StringMatch);
1751 if (IS_NULL(match)) throw MakeRangeError(kExpectedLocation, tzID);
1753 var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]);
1755 while (!IS_UNDEFINED(match[i]) && i < match.length) {
1756 result = result + '_' + toTitleCaseWord(match[i]);
1764 * Initializes the given object so it's a valid BreakIterator instance.
1765 * Useful for subclassing.
1767 function initializeBreakIterator(iterator, locales, options) {
1768 if (%IsInitializedIntlObject(iterator)) {
1769 throw MakeTypeError(kReinitializeIntl, "v8BreakIterator");
1772 if (IS_UNDEFINED(options)) {
1776 var getOption = getGetOption(options, 'breakiterator');
1778 var internalOptions = {};
1780 defineWEProperty(internalOptions, 'type', getOption(
1781 'type', 'string', ['character', 'word', 'sentence', 'line'], 'word'));
1783 var locale = resolveLocale('breakiterator', locales, options);
1784 var resolved = ObjectDefineProperties({}, {
1785 requestedLocale: {value: locale.locale, writable: true},
1786 type: {value: internalOptions.type, writable: true},
1787 locale: {writable: true}
1790 var internalIterator = %CreateBreakIterator(locale.locale,
1794 %MarkAsInitializedIntlObjectOfType(iterator, 'breakiterator',
1796 ObjectDefineProperty(iterator, 'resolved', {value: resolved});
1803 * Constructs Intl.v8BreakIterator object given optional locales and options
1808 %AddNamedProperty(Intl, 'v8BreakIterator', function() {
1809 var locales = %_Arguments(0);
1810 var options = %_Arguments(1);
1812 if (!this || this === Intl) {
1813 // Constructor is called as a function.
1814 return new Intl.v8BreakIterator(locales, options);
1817 return initializeBreakIterator($toObject(this), locales, options);
1824 * BreakIterator resolvedOptions method.
1826 %AddNamedProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions',
1828 if (%_IsConstructCall()) {
1829 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1832 if (!%IsInitializedIntlObjectOfType(this, 'breakiterator')) {
1833 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "v8BreakIterator");
1836 var segmenter = this;
1837 var locale = getOptimalLanguageTag(segmenter.resolved.requestedLocale,
1838 segmenter.resolved.locale);
1842 type: segmenter.resolved.type
1847 SetFunctionName(Intl.v8BreakIterator.prototype.resolvedOptions,
1849 %FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions);
1850 %SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions);
1854 * Returns the subset of the given locale list for which this locale list
1855 * has a matching (possibly fallback) locale. Locales appear in the same
1856 * order in the returned list as in the input list.
1857 * Options are optional parameter.
1859 %AddNamedProperty(Intl.v8BreakIterator, 'supportedLocalesOf',
1861 if (%_IsConstructCall()) {
1862 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1865 return supportedLocalesOf('breakiterator', locales, %_Arguments(1));
1869 SetFunctionName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf');
1870 %FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf);
1871 %SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf);
1875 * Adopts text to segment using the iterator. Old text, if present,
1878 function adoptText(iterator, text) {
1879 %BreakIteratorAdoptText(%GetImplFromInitializedIntlObject(iterator),
1880 GlobalString(text));
1885 * Returns index of the first break in the string and moves current pointer.
1887 function first(iterator) {
1888 return %BreakIteratorFirst(%GetImplFromInitializedIntlObject(iterator));
1893 * Returns the index of the next break and moves the pointer.
1895 function next(iterator) {
1896 return %BreakIteratorNext(%GetImplFromInitializedIntlObject(iterator));
1901 * Returns index of the current break.
1903 function current(iterator) {
1904 return %BreakIteratorCurrent(%GetImplFromInitializedIntlObject(iterator));
1909 * Returns type of the current break.
1911 function breakType(iterator) {
1912 return %BreakIteratorBreakType(%GetImplFromInitializedIntlObject(iterator));
1916 addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1);
1917 addBoundMethod(Intl.v8BreakIterator, 'first', first, 0);
1918 addBoundMethod(Intl.v8BreakIterator, 'next', next, 0);
1919 addBoundMethod(Intl.v8BreakIterator, 'current', current, 0);
1920 addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0);
1922 // Save references to Intl objects and methods we use, for added security.
1923 var savedObjects = {
1924 'collator': Intl.Collator,
1925 'numberformat': Intl.NumberFormat,
1926 'dateformatall': Intl.DateTimeFormat,
1927 'dateformatdate': Intl.DateTimeFormat,
1928 'dateformattime': Intl.DateTimeFormat
1932 // Default (created with undefined locales and options parameters) collator,
1933 // number and date format instances. They'll be created as needed.
1934 var defaultObjects = {
1935 'collator': UNDEFINED,
1936 'numberformat': UNDEFINED,
1937 'dateformatall': UNDEFINED,
1938 'dateformatdate': UNDEFINED,
1939 'dateformattime': UNDEFINED,
1944 * Returns cached or newly created instance of a given service.
1945 * We cache only default instances (where no locales or options are provided).
1947 function cachedOrNewService(service, locales, options, defaults) {
1948 var useOptions = (IS_UNDEFINED(defaults)) ? options : defaults;
1949 if (IS_UNDEFINED(locales) && IS_UNDEFINED(options)) {
1950 if (IS_UNDEFINED(defaultObjects[service])) {
1951 defaultObjects[service] = new savedObjects[service](locales, useOptions);
1953 return defaultObjects[service];
1955 return new savedObjects[service](locales, useOptions);
1959 function OverrideFunction(object, name, f) {
1960 %CheckIsBootstrapping();
1961 ObjectDefineProperty(object, name, { value: f,
1964 enumerable: false });
1965 SetFunctionName(f, name);
1966 %FunctionRemovePrototype(f);
1971 * Compares this and that, and returns less than 0, 0 or greater than 0 value.
1972 * Overrides the built-in method.
1974 OverrideFunction(GlobalString.prototype, 'localeCompare', function(that) {
1975 if (%_IsConstructCall()) {
1976 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1979 if (IS_NULL_OR_UNDEFINED(this)) {
1980 throw MakeTypeError(kMethodInvokedOnNullOrUndefined);
1983 var locales = %_Arguments(1);
1984 var options = %_Arguments(2);
1985 var collator = cachedOrNewService('collator', locales, options);
1986 return compare(collator, this, that);
1992 * Unicode normalization. This method is called with one argument that
1993 * specifies the normalization form.
1994 * If none is specified, "NFC" is assumed.
1995 * If the form is not one of "NFC", "NFD", "NFKC", or "NFKD", then throw
1996 * a RangeError Exception.
1998 OverrideFunction(GlobalString.prototype, 'normalize', function(form) {
1999 if (%_IsConstructCall()) {
2000 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2003 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
2005 form = IS_UNDEFINED(form) ? 'NFC' : form;
2007 var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD'];
2009 var normalizationForm =
2010 %_CallFunction(NORMALIZATION_FORMS, form, ArrayIndexOf);
2011 if (normalizationForm === -1) {
2012 throw MakeRangeError(kNormalizationForm,
2013 %_CallFunction(NORMALIZATION_FORMS, ', ', ArrayJoin));
2016 return %StringNormalize(this, normalizationForm);
2022 * Formats a Number object (this) using locale and options values.
2023 * If locale or options are omitted, defaults are used.
2025 OverrideFunction(GlobalNumber.prototype, 'toLocaleString', function() {
2026 if (%_IsConstructCall()) {
2027 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2030 if (!(this instanceof GlobalNumber) && typeof(this) !== 'number') {
2031 throw MakeTypeError(kMethodInvokedOnWrongType, "Number");
2034 var locales = %_Arguments(0);
2035 var options = %_Arguments(1);
2036 var numberFormat = cachedOrNewService('numberformat', locales, options);
2037 return formatNumber(numberFormat, this);
2043 * Returns actual formatted date or fails if date parameter is invalid.
2045 function toLocaleDateTime(date, locales, options, required, defaults, service) {
2046 if (!(date instanceof GlobalDate)) {
2047 throw MakeTypeError(kMethodInvokedOnWrongType, "Date");
2050 if (IsNaN(date)) return 'Invalid Date';
2052 var internalOptions = toDateTimeOptions(options, required, defaults);
2055 cachedOrNewService(service, locales, options, internalOptions);
2057 return formatDate(dateFormat, date);
2062 * Formats a Date object (this) using locale and options values.
2063 * If locale or options are omitted, defaults are used - both date and time are
2064 * present in the output.
2066 OverrideFunction(GlobalDate.prototype, 'toLocaleString', function() {
2067 if (%_IsConstructCall()) {
2068 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2071 var locales = %_Arguments(0);
2072 var options = %_Arguments(1);
2073 return toLocaleDateTime(
2074 this, locales, options, 'any', 'all', 'dateformatall');
2080 * Formats a Date object (this) using locale and options values.
2081 * If locale or options are omitted, defaults are used - only date is present
2084 OverrideFunction(GlobalDate.prototype, 'toLocaleDateString', function() {
2085 if (%_IsConstructCall()) {
2086 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2089 var locales = %_Arguments(0);
2090 var options = %_Arguments(1);
2091 return toLocaleDateTime(
2092 this, locales, options, 'date', 'date', 'dateformatdate');
2098 * Formats a Date object (this) using locale and options values.
2099 * If locale or options are omitted, defaults are used - only time is present
2102 OverrideFunction(GlobalDate.prototype, 'toLocaleTimeString', function() {
2103 if (%_IsConstructCall()) {
2104 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2107 var locales = %_Arguments(0);
2108 var options = %_Arguments(1);
2109 return toLocaleDateTime(
2110 this, locales, options, 'time', 'time', 'dateformattime');