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.
4 // limitations under the License.
6 // ECMAScript 402 API implementation.
9 * Intl object is a single object that has some named properties,
10 * all of which are constructors.
12 $Object.defineProperty(global, "Intl", { enumerable: false, value: (function() {
18 var undefined = global.undefined;
20 var AVAILABLE_SERVICES = ['collator',
25 var NORMALIZATION_FORMS = ['NFC',
31 * Caches available locales for each service.
33 var AVAILABLE_LOCALES = {
34 'collator': undefined,
35 'numberformat': undefined,
36 'dateformat': undefined,
37 'breakiterator': undefined
41 * Caches default ICU locale.
43 var DEFAULT_ICU_LOCALE = undefined;
46 * Unicode extension regular expression.
48 var UNICODE_EXTENSION_RE = undefined;
50 function GetUnicodeExtensionRE() {
51 if (UNICODE_EXTENSION_RE === undefined) {
52 UNICODE_EXTENSION_RE = new $RegExp('-u(-[a-z0-9]{2,8})+', 'g');
54 return UNICODE_EXTENSION_RE;
58 * Matches any Unicode extension.
60 var ANY_EXTENSION_RE = undefined;
62 function GetAnyExtensionRE() {
63 if (ANY_EXTENSION_RE === undefined) {
64 ANY_EXTENSION_RE = new $RegExp('-[a-z0-9]{1}-.*', 'g');
66 return ANY_EXTENSION_RE;
70 * Replace quoted text (single quote, anything but the quote and quote again).
72 var QUOTED_STRING_RE = undefined;
74 function GetQuotedStringRE() {
75 if (QUOTED_STRING_RE === undefined) {
76 QUOTED_STRING_RE = new $RegExp("'[^']+'", 'g');
78 return QUOTED_STRING_RE;
82 * Matches valid service name.
84 var SERVICE_RE = undefined;
86 function GetServiceRE() {
87 if (SERVICE_RE === undefined) {
89 new $RegExp('^(collator|numberformat|dateformat|breakiterator)$');
95 * Validates a language tag against bcp47 spec.
96 * Actual value is assigned on first run.
98 var LANGUAGE_TAG_RE = undefined;
100 function GetLanguageTagRE() {
101 if (LANGUAGE_TAG_RE === undefined) {
102 BuildLanguageTagREs();
104 return LANGUAGE_TAG_RE;
108 * Helps find duplicate variants in the language tag.
110 var LANGUAGE_VARIANT_RE = undefined;
112 function GetLanguageVariantRE() {
113 if (LANGUAGE_VARIANT_RE === undefined) {
114 BuildLanguageTagREs();
116 return LANGUAGE_VARIANT_RE;
120 * Helps find duplicate singletons in the language tag.
122 var LANGUAGE_SINGLETON_RE = undefined;
124 function GetLanguageSingletonRE() {
125 if (LANGUAGE_SINGLETON_RE === undefined) {
126 BuildLanguageTagREs();
128 return LANGUAGE_SINGLETON_RE;
132 * Matches valid IANA time zone names.
134 var TIMEZONE_NAME_CHECK_RE = undefined;
136 function GetTimezoneNameCheckRE() {
137 if (TIMEZONE_NAME_CHECK_RE === undefined) {
138 TIMEZONE_NAME_CHECK_RE =
139 new $RegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$');
141 return TIMEZONE_NAME_CHECK_RE;
145 * Maps ICU calendar names into LDML type.
147 var ICU_CALENDAR_MAP = {
148 'gregorian': 'gregory',
149 'japanese': 'japanese',
150 'buddhist': 'buddhist',
152 'persian': 'persian',
153 'islamic-civil': 'islamicc',
154 'islamic': 'islamic',
156 'chinese': 'chinese',
159 'ethiopic': 'ethiopic',
160 'ethiopic-amete-alem': 'ethioaa'
164 * Map of Unicode extensions to option properties, and their values and types,
167 var COLLATOR_KEY_MAP = {
168 'kn': {'property': 'numeric', 'type': 'boolean'},
169 'kf': {'property': 'caseFirst', 'type': 'string',
170 'values': ['false', 'lower', 'upper']}
174 * Map of Unicode extensions to option properties, and their values and types,
175 * for a number format.
177 var NUMBER_FORMAT_KEY_MAP = {
178 'nu': {'property': undefined, 'type': 'string'}
182 * Map of Unicode extensions to option properties, and their values and types,
183 * for a date/time format.
185 var DATETIME_FORMAT_KEY_MAP = {
186 'ca': {'property': undefined, 'type': 'string'},
187 'nu': {'property': undefined, 'type': 'string'}
191 * Allowed -u-co- values. List taken from:
192 * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml
194 var ALLOWED_CO_VALUES = [
195 'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic',
196 'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin'
200 * Error message for when function object is created with new and it's not
203 var ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR =
204 'Function object that\'s not a constructor was created with new';
208 * Adds bound method to the prototype of the given object.
210 function addBoundMethod(obj, methodName, implementation, length) {
212 if (!%IsInitializedIntlObject(this)) {
213 throw new $TypeError('Method ' + methodName + ' called on a ' +
214 'non-object or on a wrong type of object.');
216 var internalName = '__bound' + methodName + '__';
217 if (this[internalName] === undefined) {
220 if (length === undefined || length === 2) {
221 boundMethod = function(x, y) {
222 if (%_IsConstructCall()) {
223 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
225 return implementation(that, x, y);
227 } else if (length === 1) {
228 boundMethod = function(x) {
229 if (%_IsConstructCall()) {
230 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
232 return implementation(that, x);
235 boundMethod = function() {
236 if (%_IsConstructCall()) {
237 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
239 // DateTimeFormat.format needs to be 0 arg method, but can stil
240 // receive optional dateValue param. If one was provided, pass it
242 if (%_ArgumentsLength() > 0) {
243 return implementation(that, %_Arguments(0));
245 return implementation(that);
249 %FunctionSetName(boundMethod, internalName);
250 %FunctionRemovePrototype(boundMethod);
251 %SetNativeFlag(boundMethod);
252 this[internalName] = boundMethod;
254 return this[internalName];
257 %FunctionSetName(getter, methodName);
258 %FunctionRemovePrototype(getter);
259 %SetNativeFlag(getter);
261 ObjectDefineProperty(obj.prototype, methodName, {
270 * Returns an intersection of locales and service supported locales.
271 * Parameter locales is treated as a priority list.
273 function supportedLocalesOf(service, locales, options) {
274 if (IS_NULL(service.match(GetServiceRE()))) {
275 throw new $Error('Internal error, wrong service type: ' + service);
278 // Provide defaults if matcher was not specified.
279 if (options === undefined) {
282 options = ToObject(options);
285 var matcher = options.localeMatcher;
286 if (matcher !== undefined) {
287 matcher = $String(matcher);
288 if (matcher !== 'lookup' && matcher !== 'best fit') {
289 throw new $RangeError('Illegal value for localeMatcher:' + matcher);
292 matcher = 'best fit';
295 var requestedLocales = initializeLocaleList(locales);
297 // Cache these, they don't ever change per service.
298 if (AVAILABLE_LOCALES[service] === undefined) {
299 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
302 // Use either best fit or lookup algorithm to match locales.
303 if (matcher === 'best fit') {
304 return initializeLocaleList(bestFitSupportedLocalesOf(
305 requestedLocales, AVAILABLE_LOCALES[service]));
308 return initializeLocaleList(lookupSupportedLocalesOf(
309 requestedLocales, AVAILABLE_LOCALES[service]));
314 * Returns the subset of the provided BCP 47 language priority list for which
315 * this service has a matching locale when using the BCP 47 Lookup algorithm.
316 * Locales appear in the same order in the returned list as in the input list.
318 function lookupSupportedLocalesOf(requestedLocales, availableLocales) {
319 var matchedLocales = [];
320 for (var i = 0; i < requestedLocales.length; ++i) {
321 // Remove -u- extension.
322 var locale = requestedLocales[i].replace(GetUnicodeExtensionRE(), '');
324 if (availableLocales[locale] !== undefined) {
325 // Push requested locale not the resolved one.
326 matchedLocales.push(requestedLocales[i]);
329 // Truncate locale if possible, if not break.
330 var pos = locale.lastIndexOf('-');
334 locale = locale.substring(0, pos);
338 return matchedLocales;
343 * Returns the subset of the provided BCP 47 language priority list for which
344 * this service has a matching locale when using the implementation
345 * dependent algorithm.
346 * Locales appear in the same order in the returned list as in the input list.
348 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) {
349 // TODO(cira): implement better best fit algorithm.
350 return lookupSupportedLocalesOf(requestedLocales, availableLocales);
355 * Returns a getOption function that extracts property value for given
356 * options object. If property is missing it returns defaultValue. If value
357 * is out of range for that property it throws RangeError.
359 function getGetOption(options, caller) {
360 if (options === undefined) {
361 throw new $Error('Internal ' + caller + ' error. ' +
362 'Default options are missing.');
365 var getOption = function getOption(property, type, values, defaultValue) {
366 if (options[property] !== undefined) {
367 var value = options[property];
370 value = $Boolean(value);
373 value = $String(value);
376 value = $Number(value);
379 throw new $Error('Internal error. Wrong value type.');
381 if (values !== undefined && values.indexOf(value) === -1) {
382 throw new $RangeError('Value ' + value + ' out of range for ' + caller +
383 ' options property ' + property);
397 * Compares a BCP 47 language priority list requestedLocales against the locales
398 * in availableLocales and determines the best available language to meet the
399 * request. Two algorithms are available to match the locales: the Lookup
400 * algorithm described in RFC 4647 section 3.4, and an implementation dependent
401 * best-fit algorithm. Independent of the locale matching algorithm, options
402 * specified through Unicode locale extension sequences are negotiated
403 * separately, taking the caller's relevant extension keys and locale data as
404 * well as client-provided options into consideration. Returns an object with
405 * a locale property whose value is the language tag of the selected locale,
406 * and properties for each key in relevantExtensionKeys providing the selected
407 * value for that key.
409 function resolveLocale(service, requestedLocales, options) {
410 requestedLocales = initializeLocaleList(requestedLocales);
412 var getOption = getGetOption(options, service);
413 var matcher = getOption('localeMatcher', 'string',
414 ['lookup', 'best fit'], 'best fit');
416 if (matcher === 'lookup') {
417 resolved = lookupMatcher(service, requestedLocales);
419 resolved = bestFitMatcher(service, requestedLocales);
427 * Returns best matched supported locale and extension info using basic
430 function lookupMatcher(service, requestedLocales) {
431 if (IS_NULL(service.match(GetServiceRE()))) {
432 throw new $Error('Internal error, wrong service type: ' + service);
435 // Cache these, they don't ever change per service.
436 if (AVAILABLE_LOCALES[service] === undefined) {
437 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
440 for (var i = 0; i < requestedLocales.length; ++i) {
441 // Remove all extensions.
442 var locale = requestedLocales[i].replace(GetAnyExtensionRE(), '');
444 if (AVAILABLE_LOCALES[service][locale] !== undefined) {
445 // Return the resolved locale and extension.
446 var extensionMatch = requestedLocales[i].match(GetUnicodeExtensionRE());
447 var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0];
448 return {'locale': locale, 'extension': extension, 'position': i};
450 // Truncate locale if possible.
451 var pos = locale.lastIndexOf('-');
455 locale = locale.substring(0, pos);
459 // Didn't find a match, return default.
460 if (DEFAULT_ICU_LOCALE === undefined) {
461 DEFAULT_ICU_LOCALE = %GetDefaultICULocale();
464 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1};
469 * Returns best matched supported locale and extension info using
470 * implementation dependend algorithm.
472 function bestFitMatcher(service, requestedLocales) {
473 // TODO(cira): implement better best fit algorithm.
474 return lookupMatcher(service, requestedLocales);
479 * Parses Unicode extension into key - value map.
480 * Returns empty object if the extension string is invalid.
481 * We are not concerned with the validity of the values at this point.
483 function parseExtension(extension) {
484 var extensionSplit = extension.split('-');
486 // Assume ['', 'u', ...] input, but don't throw.
487 if (extensionSplit.length <= 2 ||
488 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
492 // Key is {2}alphanum, value is {3,8}alphanum.
493 // Some keys may not have explicit values (booleans).
494 var extensionMap = {};
495 var previousKey = undefined;
496 for (var i = 2; i < extensionSplit.length; ++i) {
497 var length = extensionSplit[i].length;
498 var element = extensionSplit[i];
500 extensionMap[element] = undefined;
501 previousKey = element;
502 } else if (length >= 3 && length <=8 && previousKey !== undefined) {
503 extensionMap[previousKey] = element;
504 previousKey = undefined;
506 // There is a value that's too long, or that doesn't have a key.
516 * Populates internalOptions object with boolean key-value pairs
517 * from extensionMap and options.
518 * Returns filtered extension (number and date format constructors use
519 * Unicode extensions for passing parameters to ICU).
520 * It's used for extension-option pairs only, e.g. kn-normalization, but not
521 * for 'sensitivity' since it doesn't have extension equivalent.
522 * Extensions like nu and ca don't have options equivalent, so we place
523 * undefined in the map.property to denote that.
525 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
528 var updateExtension = function updateExtension(key, value) {
529 return '-' + key + '-' + $String(value);
532 var updateProperty = function updateProperty(property, type, value) {
533 if (type === 'boolean' && (typeof value === 'string')) {
534 value = (value === 'true') ? true : false;
537 if (property !== undefined) {
538 defineWEProperty(outOptions, property, value);
542 for (var key in keyValues) {
543 if (keyValues.hasOwnProperty(key)) {
544 var value = undefined;
545 var map = keyValues[key];
546 if (map.property !== undefined) {
547 // This may return true if user specifies numeric: 'false', since
548 // Boolean('nonempty') === true.
549 value = getOption(map.property, map.type, map.values);
551 if (value !== undefined) {
552 updateProperty(map.property, map.type, value);
553 extension += updateExtension(key, value);
556 // User options didn't have it, check Unicode extension.
557 // Here we want to convert strings 'true', 'false' into proper Boolean
558 // values (not a user error).
559 if (extensionMap.hasOwnProperty(key)) {
560 value = extensionMap[key];
561 if (value !== undefined) {
562 updateProperty(map.property, map.type, value);
563 extension += updateExtension(key, value);
564 } else if (map.type === 'boolean') {
565 // Boolean keys are allowed not to have values in Unicode extension.
566 // Those default to true.
567 updateProperty(map.property, map.type, true);
568 extension += updateExtension(key, true);
574 return extension === ''? '' : '-u' + extension;
579 * Converts all OwnProperties into
580 * configurable: false, writable: false, enumerable: true.
582 function freezeArray(array) {
583 array.forEach(function(element, index) {
584 ObjectDefineProperty(array, index, {value: element,
590 ObjectDefineProperty(array, 'length', {value: array.length,
597 * It's sometimes desireable to leave user requested locale instead of ICU
598 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
599 * one, if that was what user requested).
600 * This function returns user specified tag if its maximized form matches ICU
601 * resolved locale. If not we return ICU result.
603 function getOptimalLanguageTag(original, resolved) {
604 // Returns Array<Object>, where each object has maximized and base properties.
605 // Maximized: zh -> zh-Hans-CN
606 // Base: zh-CN-u-ca-gregory -> zh-CN
607 // Take care of grandfathered or simple cases.
608 if (original === resolved) {
612 var locales = %GetLanguageTagVariants([original, resolved]);
613 if (locales[0].maximized !== locales[1].maximized) {
617 // Preserve extensions of resolved locale, but swap base tags with original.
618 var resolvedBase = new $RegExp('^' + locales[1].base);
619 return resolved.replace(resolvedBase, locales[0].base);
624 * Returns an Object that contains all of supported locales for a given
626 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ
627 * that is supported. This is required by the spec.
629 function getAvailableLocalesOf(service) {
630 var available = %AvailableLocalesOf(service);
632 for (var i in available) {
633 if (available.hasOwnProperty(i)) {
634 var parts = i.match(/^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/);
635 if (parts !== null) {
636 // Build xx-ZZ. We don't care about the actual value,
637 // as long it's not undefined.
638 available[parts[1] + '-' + parts[3]] = null;
648 * Defines a property and sets writable and enumerable to true.
649 * Configurable is false by default.
651 function defineWEProperty(object, property, value) {
652 ObjectDefineProperty(object, property,
653 {value: value, writable: true, enumerable: true});
658 * Adds property to an object if the value is not undefined.
659 * Sets configurable descriptor to false.
661 function addWEPropertyIfDefined(object, property, value) {
662 if (value !== undefined) {
663 defineWEProperty(object, property, value);
669 * Defines a property and sets writable, enumerable and configurable to true.
671 function defineWECProperty(object, property, value) {
672 ObjectDefineProperty(object, property,
676 configurable: true});
681 * Adds property to an object if the value is not undefined.
682 * Sets all descriptors to true.
684 function addWECPropertyIfDefined(object, property, value) {
685 if (value !== undefined) {
686 defineWECProperty(object, property, value);
692 * Returns titlecased word, aMeRricA -> America.
694 function toTitleCaseWord(word) {
695 return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase();
699 * Canonicalizes the language tag, or throws in case the tag is invalid.
701 function canonicalizeLanguageTag(localeID) {
702 // null is typeof 'object' so we have to do extra check.
703 if (typeof localeID !== 'string' && typeof localeID !== 'object' ||
705 throw new $TypeError('Language ID should be string or object.');
708 var localeString = $String(localeID);
710 if (isValidLanguageTag(localeString) === false) {
711 throw new $RangeError('Invalid language tag: ' + localeString);
714 // This call will strip -kn but not -kn-true extensions.
715 // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265.
716 // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after
717 // upgrade to ICU 4.9.
718 var tag = %CanonicalizeLanguageTag(localeString);
719 if (tag === 'invalid-tag') {
720 throw new $RangeError('Invalid language tag: ' + localeString);
728 * Returns an array where all locales are canonicalized and duplicates removed.
729 * Throws on locales that are not well formed BCP47 tags.
731 function initializeLocaleList(locales) {
733 if (locales === undefined) {
734 // Constructor is called without arguments.
737 // We allow single string localeID.
738 if (typeof locales === 'string') {
739 seen.push(canonicalizeLanguageTag(locales));
740 return freezeArray(seen);
743 var o = ToObject(locales);
744 // Converts it to UInt32 (>>> is shr on 32bit integers).
745 var len = o.length >>> 0;
747 for (var k = 0; k < len; k++) {
751 var tag = canonicalizeLanguageTag(value);
753 if (seen.indexOf(tag) === -1) {
760 return freezeArray(seen);
765 * Validates the language tag. Section 2.2.9 of the bcp47 spec
766 * defines a valid tag.
768 * ICU is too permissible and lets invalid tags, like
769 * hant-cmn-cn, through.
771 * Returns false if the language tag is invalid.
773 function isValidLanguageTag(locale) {
774 // Check if it's well-formed, including grandfadered tags.
775 if (GetLanguageTagRE().test(locale) === false) {
779 // Just return if it's a x- form. It's all private.
780 if (locale.indexOf('x-') === 0) {
784 // Check if there are any duplicate variants or singletons (extensions).
786 // Remove private use section.
787 locale = locale.split(/-x-/)[0];
789 // Skip language since it can match variant regex, so we start from 1.
790 // We are matching i-klingon here, but that's ok, since i-klingon-klingon
791 // is not valid and would fail LANGUAGE_TAG_RE test.
794 var parts = locale.split(/-/);
795 for (var i = 1; i < parts.length; i++) {
796 var value = parts[i];
797 if (GetLanguageVariantRE().test(value) === true && extensions.length === 0) {
798 if (variants.indexOf(value) === -1) {
799 variants.push(value);
805 if (GetLanguageSingletonRE().test(value) === true) {
806 if (extensions.indexOf(value) === -1) {
807 extensions.push(value);
819 * Builds a regular expresion that validates the language tag
820 * against bcp47 spec.
821 * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF.
822 * Runs on load and initializes the global REs.
824 function BuildLanguageTagREs() {
825 var alpha = '[a-zA-Z]';
827 var alphanum = '(' + alpha + '|' + digit + ')';
828 var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' +
829 'zh-min|zh-min-nan|zh-xiang)';
830 var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' +
831 'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' +
832 'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)';
833 var grandfathered = '(' + irregular + '|' + regular + ')';
834 var privateUse = '(x(-' + alphanum + '{1,8})+)';
836 var singleton = '(' + digit + '|[A-WY-Za-wy-z])';
837 LANGUAGE_SINGLETON_RE = new $RegExp('^' + singleton + '$', 'i');
839 var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)';
841 var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))';
842 LANGUAGE_VARIANT_RE = new $RegExp('^' + variant + '$', 'i');
844 var region = '(' + alpha + '{2}|' + digit + '{3})';
845 var script = '(' + alpha + '{4})';
846 var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})';
847 var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' +
849 var langTag = language + '(-' + script + ')?(-' + region + ')?(-' +
850 variant + ')*(-' + extension + ')*(-' + privateUse + ')?';
853 '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$';
854 LANGUAGE_TAG_RE = new $RegExp(languageTag, 'i');
858 * Initializes the given object so it's a valid Collator instance.
859 * Useful for subclassing.
861 function initializeCollator(collator, locales, options) {
862 if (%IsInitializedIntlObject(collator)) {
863 throw new $TypeError('Trying to re-initialize Collator object.');
866 if (options === undefined) {
870 var getOption = getGetOption(options, 'collator');
872 var internalOptions = {};
874 defineWEProperty(internalOptions, 'usage', getOption(
875 'usage', 'string', ['sort', 'search'], 'sort'));
877 var sensitivity = getOption('sensitivity', 'string',
878 ['base', 'accent', 'case', 'variant']);
879 if (sensitivity === undefined && internalOptions.usage === 'sort') {
880 sensitivity = 'variant';
882 defineWEProperty(internalOptions, 'sensitivity', sensitivity);
884 defineWEProperty(internalOptions, 'ignorePunctuation', getOption(
885 'ignorePunctuation', 'boolean', undefined, false));
887 var locale = resolveLocale('collator', locales, options);
889 // ICU can't take kb, kc... parameters through localeID, so we need to pass
891 // One exception is -co- which has to be part of the extension, but only for
892 // usage: sort, and its value can't be 'standard' or 'search'.
893 var extensionMap = parseExtension(locale.extension);
895 options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions);
897 var collation = 'default';
899 if (extensionMap.hasOwnProperty('co') && internalOptions.usage === 'sort') {
900 if (ALLOWED_CO_VALUES.indexOf(extensionMap.co) !== -1) {
901 extension = '-u-co-' + extensionMap.co;
902 // ICU can't tell us what the collation is, so save user's input.
903 collation = extensionMap.co;
905 } else if (internalOptions.usage === 'search') {
906 extension = '-u-co-search';
908 defineWEProperty(internalOptions, 'collation', collation);
910 var requestedLocale = locale.locale + extension;
912 // We define all properties C++ code may produce, to prevent security
913 // problems. If malicious user decides to redefine Object.prototype.locale
914 // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us").
915 // ObjectDefineProperties will either succeed defining or throw an error.
916 var resolved = ObjectDefineProperties({}, {
917 caseFirst: {writable: true},
918 collation: {value: internalOptions.collation, writable: true},
919 ignorePunctuation: {writable: true},
920 locale: {writable: true},
921 numeric: {writable: true},
922 requestedLocale: {value: requestedLocale, writable: true},
923 sensitivity: {writable: true},
924 strength: {writable: true},
925 usage: {value: internalOptions.usage, writable: true}
928 var internalCollator = %CreateCollator(requestedLocale,
932 // Writable, configurable and enumerable are set to false by default.
933 %MarkAsInitializedIntlObjectOfType(collator, 'collator', internalCollator);
934 ObjectDefineProperty(collator, 'resolved', {value: resolved});
941 * Constructs Intl.Collator object given optional locales and options
946 %SetProperty(Intl, 'Collator', function() {
947 var locales = %_Arguments(0);
948 var options = %_Arguments(1);
950 if (!this || this === Intl) {
951 // Constructor is called as a function.
952 return new Intl.Collator(locales, options);
955 return initializeCollator(ToObject(this), locales, options);
962 * Collator resolvedOptions method.
964 %SetProperty(Intl.Collator.prototype, 'resolvedOptions', function() {
965 if (%_IsConstructCall()) {
966 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
969 if (!%IsInitializedIntlObjectOfType(this, 'collator')) {
970 throw new $TypeError('resolvedOptions method called on a non-object ' +
971 'or on a object that is not Intl.Collator.');
975 var locale = getOptimalLanguageTag(coll.resolved.requestedLocale,
976 coll.resolved.locale);
980 usage: coll.resolved.usage,
981 sensitivity: coll.resolved.sensitivity,
982 ignorePunctuation: coll.resolved.ignorePunctuation,
983 numeric: coll.resolved.numeric,
984 caseFirst: coll.resolved.caseFirst,
985 collation: coll.resolved.collation
990 %FunctionSetName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions');
991 %FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions);
992 %SetNativeFlag(Intl.Collator.prototype.resolvedOptions);
996 * Returns the subset of the given locale list for which this locale list
997 * has a matching (possibly fallback) locale. Locales appear in the same
998 * order in the returned list as in the input list.
999 * Options are optional parameter.
1001 %SetProperty(Intl.Collator, 'supportedLocalesOf', function(locales) {
1002 if (%_IsConstructCall()) {
1003 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1006 return supportedLocalesOf('collator', locales, %_Arguments(1));
1010 %FunctionSetName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf');
1011 %FunctionRemovePrototype(Intl.Collator.supportedLocalesOf);
1012 %SetNativeFlag(Intl.Collator.supportedLocalesOf);
1016 * When the compare method is called with two arguments x and y, it returns a
1017 * Number other than NaN that represents the result of a locale-sensitive
1018 * String comparison of x with y.
1019 * The result is intended to order String values in the sort order specified
1020 * by the effective locale and collation options computed during construction
1021 * of this Collator object, and will be negative, zero, or positive, depending
1022 * on whether x comes before y in the sort order, the Strings are equal under
1023 * the sort order, or x comes after y in the sort order, respectively.
1025 function compare(collator, x, y) {
1026 return %InternalCompare(%GetImplFromInitializedIntlObject(collator),
1027 $String(x), $String(y));
1031 addBoundMethod(Intl.Collator, 'compare', compare, 2);
1034 * Verifies that the input is a well-formed ISO 4217 currency code.
1035 * Don't uppercase to test. It could convert invalid code into a valid one.
1036 * For example \u00DFP (Eszett+P) becomes SSP.
1038 function isWellFormedCurrencyCode(currency) {
1039 return typeof currency == "string" &&
1040 currency.length == 3 &&
1041 currency.match(/[^A-Za-z]/) == null;
1046 * Returns the valid digit count for a property, or throws RangeError on
1047 * a value out of the range.
1049 function getNumberOption(options, property, min, max, fallback) {
1050 var value = options[property];
1051 if (value !== undefined) {
1052 value = $Number(value);
1053 if ($isNaN(value) || value < min || value > max) {
1054 throw new $RangeError(property + ' value is out of range.');
1056 return $floor(value);
1064 * Initializes the given object so it's a valid NumberFormat instance.
1065 * Useful for subclassing.
1067 function initializeNumberFormat(numberFormat, locales, options) {
1068 if (%IsInitializedIntlObject(numberFormat)) {
1069 throw new $TypeError('Trying to re-initialize NumberFormat object.');
1072 if (options === undefined) {
1076 var getOption = getGetOption(options, 'numberformat');
1078 var locale = resolveLocale('numberformat', locales, options);
1080 var internalOptions = {};
1081 defineWEProperty(internalOptions, 'style', getOption(
1082 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal'));
1084 var currency = getOption('currency', 'string');
1085 if (currency !== undefined && !isWellFormedCurrencyCode(currency)) {
1086 throw new $RangeError('Invalid currency code: ' + currency);
1089 if (internalOptions.style === 'currency' && currency === undefined) {
1090 throw new $TypeError('Currency code is required with currency style.');
1093 var currencyDisplay = getOption(
1094 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
1095 if (internalOptions.style === 'currency') {
1096 defineWEProperty(internalOptions, 'currency', currency.toUpperCase());
1097 defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay);
1101 var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
1102 defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid);
1104 var mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0);
1105 defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd);
1107 var mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, 3);
1108 defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd);
1110 var mnsd = options['minimumSignificantDigits'];
1111 var mxsd = options['maximumSignificantDigits'];
1112 if (mnsd !== undefined || mxsd !== undefined) {
1113 mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0);
1114 defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd);
1116 mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21);
1117 defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd);
1121 defineWEProperty(internalOptions, 'useGrouping', getOption(
1122 'useGrouping', 'boolean', undefined, true));
1124 // ICU prefers options to be passed using -u- extension key/values for
1125 // number format, so we need to build that.
1126 var extensionMap = parseExtension(locale.extension);
1127 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP,
1128 getOption, internalOptions);
1130 var requestedLocale = locale.locale + extension;
1131 var resolved = ObjectDefineProperties({}, {
1132 currency: {writable: true},
1133 currencyDisplay: {writable: true},
1134 locale: {writable: true},
1135 maximumFractionDigits: {writable: true},
1136 minimumFractionDigits: {writable: true},
1137 minimumIntegerDigits: {writable: true},
1138 numberingSystem: {writable: true},
1139 requestedLocale: {value: requestedLocale, writable: true},
1140 style: {value: internalOptions.style, writable: true},
1141 useGrouping: {writable: true}
1143 if (internalOptions.hasOwnProperty('minimumSignificantDigits')) {
1144 defineWEProperty(resolved, 'minimumSignificantDigits', undefined);
1146 if (internalOptions.hasOwnProperty('maximumSignificantDigits')) {
1147 defineWEProperty(resolved, 'maximumSignificantDigits', undefined);
1149 var formatter = %CreateNumberFormat(requestedLocale,
1153 // We can't get information about number or currency style from ICU, so we
1154 // assume user request was fulfilled.
1155 if (internalOptions.style === 'currency') {
1156 ObjectDefineProperty(resolved, 'currencyDisplay', {value: currencyDisplay,
1160 %MarkAsInitializedIntlObjectOfType(numberFormat, 'numberformat', formatter);
1161 ObjectDefineProperty(numberFormat, 'resolved', {value: resolved});
1163 return numberFormat;
1168 * Constructs Intl.NumberFormat object given optional locales and options
1173 %SetProperty(Intl, 'NumberFormat', function() {
1174 var locales = %_Arguments(0);
1175 var options = %_Arguments(1);
1177 if (!this || this === Intl) {
1178 // Constructor is called as a function.
1179 return new Intl.NumberFormat(locales, options);
1182 return initializeNumberFormat(ToObject(this), locales, options);
1189 * NumberFormat resolvedOptions method.
1191 %SetProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() {
1192 if (%_IsConstructCall()) {
1193 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1196 if (!%IsInitializedIntlObjectOfType(this, 'numberformat')) {
1197 throw new $TypeError('resolvedOptions method called on a non-object' +
1198 ' or on a object that is not Intl.NumberFormat.');
1202 var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1203 format.resolved.locale);
1207 numberingSystem: format.resolved.numberingSystem,
1208 style: format.resolved.style,
1209 useGrouping: format.resolved.useGrouping,
1210 minimumIntegerDigits: format.resolved.minimumIntegerDigits,
1211 minimumFractionDigits: format.resolved.minimumFractionDigits,
1212 maximumFractionDigits: format.resolved.maximumFractionDigits,
1215 if (result.style === 'currency') {
1216 defineWECProperty(result, 'currency', format.resolved.currency);
1217 defineWECProperty(result, 'currencyDisplay',
1218 format.resolved.currencyDisplay);
1221 if (format.resolved.hasOwnProperty('minimumSignificantDigits')) {
1222 defineWECProperty(result, 'minimumSignificantDigits',
1223 format.resolved.minimumSignificantDigits);
1226 if (format.resolved.hasOwnProperty('maximumSignificantDigits')) {
1227 defineWECProperty(result, 'maximumSignificantDigits',
1228 format.resolved.maximumSignificantDigits);
1235 %FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions,
1237 %FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions);
1238 %SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions);
1242 * Returns the subset of the given locale list for which this locale list
1243 * has a matching (possibly fallback) locale. Locales appear in the same
1244 * order in the returned list as in the input list.
1245 * Options are optional parameter.
1247 %SetProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) {
1248 if (%_IsConstructCall()) {
1249 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1252 return supportedLocalesOf('numberformat', locales, %_Arguments(1));
1256 %FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf');
1257 %FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf);
1258 %SetNativeFlag(Intl.NumberFormat.supportedLocalesOf);
1262 * Returns a String value representing the result of calling ToNumber(value)
1263 * according to the effective locale and the formatting options of this
1266 function formatNumber(formatter, value) {
1267 // Spec treats -0 and +0 as 0.
1268 var number = $Number(value) + 0;
1270 return %InternalNumberFormat(%GetImplFromInitializedIntlObject(formatter),
1276 * Returns a Number that represents string value that was passed in.
1278 function parseNumber(formatter, value) {
1279 return %InternalNumberParse(%GetImplFromInitializedIntlObject(formatter),
1284 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1);
1285 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1);
1288 * Returns a string that matches LDML representation of the options object.
1290 function toLDMLString(options) {
1291 var getOption = getGetOption(options, 'dateformat');
1293 var ldmlString = '';
1295 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']);
1296 ldmlString += appendToLDMLString(
1297 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'});
1299 option = getOption('era', 'string', ['narrow', 'short', 'long']);
1300 ldmlString += appendToLDMLString(
1301 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'});
1303 option = getOption('year', 'string', ['2-digit', 'numeric']);
1304 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'});
1306 option = getOption('month', 'string',
1307 ['2-digit', 'numeric', 'narrow', 'short', 'long']);
1308 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M',
1309 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'});
1311 option = getOption('day', 'string', ['2-digit', 'numeric']);
1312 ldmlString += appendToLDMLString(
1313 option, {'2-digit': 'dd', 'numeric': 'd'});
1315 var hr12 = getOption('hour12', 'boolean');
1316 option = getOption('hour', 'string', ['2-digit', 'numeric']);
1317 if (hr12 === undefined) {
1318 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'});
1319 } else if (hr12 === true) {
1320 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'});
1322 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'});
1325 option = getOption('minute', 'string', ['2-digit', 'numeric']);
1326 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'});
1328 option = getOption('second', 'string', ['2-digit', 'numeric']);
1329 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'});
1331 option = getOption('timeZoneName', 'string', ['short', 'long']);
1332 ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'});
1339 * Returns either LDML equivalent of the current option or empty string.
1341 function appendToLDMLString(option, pairs) {
1342 if (option !== undefined) {
1343 return pairs[option];
1351 * Returns object that matches LDML representation of the date.
1353 function fromLDMLString(ldmlString) {
1354 // First remove '' quoted text, so we lose 'Uhr' strings.
1355 ldmlString = ldmlString.replace(GetQuotedStringRE(), '');
1358 var match = ldmlString.match(/E{3,5}/g);
1359 options = appendToDateTimeObject(
1360 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'});
1362 match = ldmlString.match(/G{3,5}/g);
1363 options = appendToDateTimeObject(
1364 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'});
1366 match = ldmlString.match(/y{1,2}/g);
1367 options = appendToDateTimeObject(
1368 options, 'year', match, {y: 'numeric', yy: '2-digit'});
1370 match = ldmlString.match(/M{1,5}/g);
1371 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit',
1372 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'});
1374 // Sometimes we get L instead of M for month - standalone name.
1375 match = ldmlString.match(/L{1,5}/g);
1376 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit',
1377 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'});
1379 match = ldmlString.match(/d{1,2}/g);
1380 options = appendToDateTimeObject(
1381 options, 'day', match, {d: 'numeric', dd: '2-digit'});
1383 match = ldmlString.match(/h{1,2}/g);
1384 if (match !== null) {
1385 options['hour12'] = true;
1387 options = appendToDateTimeObject(
1388 options, 'hour', match, {h: 'numeric', hh: '2-digit'});
1390 match = ldmlString.match(/H{1,2}/g);
1391 if (match !== null) {
1392 options['hour12'] = false;
1394 options = appendToDateTimeObject(
1395 options, 'hour', match, {H: 'numeric', HH: '2-digit'});
1397 match = ldmlString.match(/m{1,2}/g);
1398 options = appendToDateTimeObject(
1399 options, 'minute', match, {m: 'numeric', mm: '2-digit'});
1401 match = ldmlString.match(/s{1,2}/g);
1402 options = appendToDateTimeObject(
1403 options, 'second', match, {s: 'numeric', ss: '2-digit'});
1405 match = ldmlString.match(/z|zzzz/g);
1406 options = appendToDateTimeObject(
1407 options, 'timeZoneName', match, {z: 'short', zzzz: 'long'});
1413 function appendToDateTimeObject(options, option, match, pairs) {
1414 if (IS_NULL(match)) {
1415 if (!options.hasOwnProperty(option)) {
1416 defineWEProperty(options, option, undefined);
1421 var property = match[0];
1422 defineWEProperty(options, option, pairs[property]);
1429 * Returns options with at least default values in it.
1431 function toDateTimeOptions(options, required, defaults) {
1432 if (options === undefined) {
1435 options = TO_OBJECT_INLINE(options);
1438 var needsDefault = true;
1439 if ((required === 'date' || required === 'any') &&
1440 (options.weekday !== undefined || options.year !== undefined ||
1441 options.month !== undefined || options.day !== undefined)) {
1442 needsDefault = false;
1445 if ((required === 'time' || required === 'any') &&
1446 (options.hour !== undefined || options.minute !== undefined ||
1447 options.second !== undefined)) {
1448 needsDefault = false;
1451 if (needsDefault && (defaults === 'date' || defaults === 'all')) {
1452 ObjectDefineProperty(options, 'year', {value: 'numeric',
1455 configurable: true});
1456 ObjectDefineProperty(options, 'month', {value: 'numeric',
1459 configurable: true});
1460 ObjectDefineProperty(options, 'day', {value: 'numeric',
1463 configurable: true});
1466 if (needsDefault && (defaults === 'time' || defaults === 'all')) {
1467 ObjectDefineProperty(options, 'hour', {value: 'numeric',
1470 configurable: true});
1471 ObjectDefineProperty(options, 'minute', {value: 'numeric',
1474 configurable: true});
1475 ObjectDefineProperty(options, 'second', {value: 'numeric',
1478 configurable: true});
1486 * Initializes the given object so it's a valid DateTimeFormat instance.
1487 * Useful for subclassing.
1489 function initializeDateTimeFormat(dateFormat, locales, options) {
1491 if (%IsInitializedIntlObject(dateFormat)) {
1492 throw new $TypeError('Trying to re-initialize DateTimeFormat object.');
1495 if (options === undefined) {
1499 var locale = resolveLocale('dateformat', locales, options);
1501 options = toDateTimeOptions(options, 'any', 'date');
1503 var getOption = getGetOption(options, 'dateformat');
1505 // We implement only best fit algorithm, but still need to check
1506 // if the formatMatcher values are in range.
1507 var matcher = getOption('formatMatcher', 'string',
1508 ['basic', 'best fit'], 'best fit');
1510 // Build LDML string for the skeleton that we pass to the formatter.
1511 var ldmlString = toLDMLString(options);
1513 // Filter out supported extension keys so we know what to put in resolved
1514 // section later on.
1515 // We need to pass calendar and number system to the method.
1516 var tz = canonicalizeTimeZoneID(options.timeZone);
1518 // ICU prefers options to be passed using -u- extension key/values, so
1519 // we need to build that.
1520 var internalOptions = {};
1521 var extensionMap = parseExtension(locale.extension);
1522 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP,
1523 getOption, internalOptions);
1525 var requestedLocale = locale.locale + extension;
1526 var resolved = ObjectDefineProperties({}, {
1527 calendar: {writable: true},
1528 day: {writable: true},
1529 era: {writable: true},
1530 hour12: {writable: true},
1531 hour: {writable: true},
1532 locale: {writable: true},
1533 minute: {writable: true},
1534 month: {writable: true},
1535 numberingSystem: {writable: true},
1536 pattern: {writable: true},
1537 requestedLocale: {value: requestedLocale, writable: true},
1538 second: {writable: true},
1539 timeZone: {writable: true},
1540 timeZoneName: {writable: true},
1541 tz: {value: tz, writable: true},
1542 weekday: {writable: true},
1543 year: {writable: true}
1546 var formatter = %CreateDateTimeFormat(
1547 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved);
1549 if (tz !== undefined && tz !== resolved.timeZone) {
1550 throw new $RangeError('Unsupported time zone specified ' + tz);
1553 %MarkAsInitializedIntlObjectOfType(dateFormat, 'dateformat', formatter);
1554 ObjectDefineProperty(dateFormat, 'resolved', {value: resolved});
1561 * Constructs Intl.DateTimeFormat object given optional locales and options
1566 %SetProperty(Intl, 'DateTimeFormat', function() {
1567 var locales = %_Arguments(0);
1568 var options = %_Arguments(1);
1570 if (!this || this === Intl) {
1571 // Constructor is called as a function.
1572 return new Intl.DateTimeFormat(locales, options);
1575 return initializeDateTimeFormat(ToObject(this), locales, options);
1582 * DateTimeFormat resolvedOptions method.
1584 %SetProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
1585 if (%_IsConstructCall()) {
1586 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1589 if (!%IsInitializedIntlObjectOfType(this, 'dateformat')) {
1590 throw new $TypeError('resolvedOptions method called on a non-object or ' +
1591 'on a object that is not Intl.DateTimeFormat.');
1595 var fromPattern = fromLDMLString(format.resolved.pattern);
1596 var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar];
1597 if (userCalendar === undefined) {
1598 // Use ICU name if we don't have a match. It shouldn't happen, but
1599 // it would be too strict to throw for this.
1600 userCalendar = format.resolved.calendar;
1603 var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1604 format.resolved.locale);
1608 numberingSystem: format.resolved.numberingSystem,
1609 calendar: userCalendar,
1610 timeZone: format.resolved.timeZone
1613 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName);
1614 addWECPropertyIfDefined(result, 'era', fromPattern.era);
1615 addWECPropertyIfDefined(result, 'year', fromPattern.year);
1616 addWECPropertyIfDefined(result, 'month', fromPattern.month);
1617 addWECPropertyIfDefined(result, 'day', fromPattern.day);
1618 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday);
1619 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12);
1620 addWECPropertyIfDefined(result, 'hour', fromPattern.hour);
1621 addWECPropertyIfDefined(result, 'minute', fromPattern.minute);
1622 addWECPropertyIfDefined(result, 'second', fromPattern.second);
1628 %FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions,
1630 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions);
1631 %SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions);
1635 * Returns the subset of the given locale list for which this locale list
1636 * has a matching (possibly fallback) locale. Locales appear in the same
1637 * order in the returned list as in the input list.
1638 * Options are optional parameter.
1640 %SetProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) {
1641 if (%_IsConstructCall()) {
1642 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1645 return supportedLocalesOf('dateformat', locales, %_Arguments(1));
1649 %FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf');
1650 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf);
1651 %SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf);
1655 * Returns a String value representing the result of calling ToNumber(date)
1656 * according to the effective locale and the formatting options of this
1659 function formatDate(formatter, dateValue) {
1661 if (dateValue === undefined) {
1662 dateMs = $Date.now();
1664 dateMs = $Number(dateValue);
1667 if (!$isFinite(dateMs)) {
1668 throw new $RangeError('Provided date is not in valid range.');
1671 return %InternalDateFormat(%GetImplFromInitializedIntlObject(formatter),
1677 * Returns a Date object representing the result of calling ToString(value)
1678 * according to the effective locale and the formatting options of this
1680 * Returns undefined if date string cannot be parsed.
1682 function parseDate(formatter, value) {
1683 return %InternalDateParse(%GetImplFromInitializedIntlObject(formatter),
1688 // 0 because date is optional argument.
1689 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0);
1690 addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1);
1694 * Returns canonical Area/Location name, or throws an exception if the zone
1695 * name is invalid IANA name.
1697 function canonicalizeTimeZoneID(tzID) {
1698 // Skip undefined zones.
1699 if (tzID === undefined) {
1703 // Special case handling (UTC, GMT).
1704 var upperID = tzID.toUpperCase();
1705 if (upperID === 'UTC' || upperID === 'GMT' ||
1706 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') {
1710 // We expect only _ and / beside ASCII letters.
1711 // All inputs should conform to Area/Location from now on.
1712 var match = GetTimezoneNameCheckRE().exec(tzID);
1713 if (IS_NULL(match)) {
1714 throw new $RangeError('Expected Area/Location for time zone, got ' + tzID);
1717 var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]);
1719 while (match[i] !== undefined && i < match.length) {
1720 result = result + '_' + toTitleCaseWord(match[i]);
1728 * Initializes the given object so it's a valid BreakIterator instance.
1729 * Useful for subclassing.
1731 function initializeBreakIterator(iterator, locales, options) {
1732 if (%IsInitializedIntlObject(iterator)) {
1733 throw new $TypeError('Trying to re-initialize v8BreakIterator object.');
1736 if (options === undefined) {
1740 var getOption = getGetOption(options, 'breakiterator');
1742 var internalOptions = {};
1744 defineWEProperty(internalOptions, 'type', getOption(
1745 'type', 'string', ['character', 'word', 'sentence', 'line'], 'word'));
1747 var locale = resolveLocale('breakiterator', locales, options);
1748 var resolved = ObjectDefineProperties({}, {
1749 requestedLocale: {value: locale.locale, writable: true},
1750 type: {value: internalOptions.type, writable: true},
1751 locale: {writable: true}
1754 var internalIterator = %CreateBreakIterator(locale.locale,
1758 %MarkAsInitializedIntlObjectOfType(iterator, 'breakiterator',
1760 ObjectDefineProperty(iterator, 'resolved', {value: resolved});
1767 * Constructs Intl.v8BreakIterator object given optional locales and options
1772 %SetProperty(Intl, 'v8BreakIterator', function() {
1773 var locales = %_Arguments(0);
1774 var options = %_Arguments(1);
1776 if (!this || this === Intl) {
1777 // Constructor is called as a function.
1778 return new Intl.v8BreakIterator(locales, options);
1781 return initializeBreakIterator(ToObject(this), locales, options);
1788 * BreakIterator resolvedOptions method.
1790 %SetProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions', function() {
1791 if (%_IsConstructCall()) {
1792 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1795 if (!%IsInitializedIntlObjectOfType(this, 'breakiterator')) {
1796 throw new $TypeError('resolvedOptions method called on a non-object or ' +
1797 'on a object that is not Intl.v8BreakIterator.');
1800 var segmenter = this;
1801 var locale = getOptimalLanguageTag(segmenter.resolved.requestedLocale,
1802 segmenter.resolved.locale);
1806 type: segmenter.resolved.type
1811 %FunctionSetName(Intl.v8BreakIterator.prototype.resolvedOptions,
1813 %FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions);
1814 %SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions);
1818 * Returns the subset of the given locale list for which this locale list
1819 * has a matching (possibly fallback) locale. Locales appear in the same
1820 * order in the returned list as in the input list.
1821 * Options are optional parameter.
1823 %SetProperty(Intl.v8BreakIterator, 'supportedLocalesOf', function(locales) {
1824 if (%_IsConstructCall()) {
1825 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1828 return supportedLocalesOf('breakiterator', locales, %_Arguments(1));
1832 %FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf');
1833 %FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf);
1834 %SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf);
1838 * Adopts text to segment using the iterator. Old text, if present,
1841 function adoptText(iterator, text) {
1842 %BreakIteratorAdoptText(%GetImplFromInitializedIntlObject(iterator),
1848 * Returns index of the first break in the string and moves current pointer.
1850 function first(iterator) {
1851 return %BreakIteratorFirst(%GetImplFromInitializedIntlObject(iterator));
1856 * Returns the index of the next break and moves the pointer.
1858 function next(iterator) {
1859 return %BreakIteratorNext(%GetImplFromInitializedIntlObject(iterator));
1864 * Returns index of the current break.
1866 function current(iterator) {
1867 return %BreakIteratorCurrent(%GetImplFromInitializedIntlObject(iterator));
1872 * Returns type of the current break.
1874 function breakType(iterator) {
1875 return %BreakIteratorBreakType(%GetImplFromInitializedIntlObject(iterator));
1879 addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1);
1880 addBoundMethod(Intl.v8BreakIterator, 'first', first, 0);
1881 addBoundMethod(Intl.v8BreakIterator, 'next', next, 0);
1882 addBoundMethod(Intl.v8BreakIterator, 'current', current, 0);
1883 addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0);
1885 // Save references to Intl objects and methods we use, for added security.
1886 var savedObjects = {
1887 'collator': Intl.Collator,
1888 'numberformat': Intl.NumberFormat,
1889 'dateformatall': Intl.DateTimeFormat,
1890 'dateformatdate': Intl.DateTimeFormat,
1891 'dateformattime': Intl.DateTimeFormat
1895 // Default (created with undefined locales and options parameters) collator,
1896 // number and date format instances. They'll be created as needed.
1897 var defaultObjects = {
1898 'collator': undefined,
1899 'numberformat': undefined,
1900 'dateformatall': undefined,
1901 'dateformatdate': undefined,
1902 'dateformattime': undefined,
1907 * Returns cached or newly created instance of a given service.
1908 * We cache only default instances (where no locales or options are provided).
1910 function cachedOrNewService(service, locales, options, defaults) {
1911 var useOptions = (defaults === undefined) ? options : defaults;
1912 if (locales === undefined && options === undefined) {
1913 if (defaultObjects[service] === undefined) {
1914 defaultObjects[service] = new savedObjects[service](locales, useOptions);
1916 return defaultObjects[service];
1918 return new savedObjects[service](locales, useOptions);
1923 * Compares this and that, and returns less than 0, 0 or greater than 0 value.
1924 * Overrides the built-in method.
1926 ObjectDefineProperty($String.prototype, 'localeCompare', {
1927 value: function(that) {
1928 if (%_IsConstructCall()) {
1929 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1932 if (IS_NULL_OR_UNDEFINED(this)) {
1933 throw new $TypeError('Method invoked on undefined or null value.');
1936 var locales = %_Arguments(1);
1937 var options = %_Arguments(2);
1938 var collator = cachedOrNewService('collator', locales, options);
1939 return compare(collator, this, that);
1945 %FunctionSetName($String.prototype.localeCompare, 'localeCompare');
1946 %FunctionRemovePrototype($String.prototype.localeCompare);
1947 %SetNativeFlag($String.prototype.localeCompare);
1951 * Unicode normalization. This method is called with one argument that
1952 * specifies the normalization form.
1953 * If none is specified, "NFC" is assumed.
1954 * If the form is not one of "NFC", "NFD", "NFKC", or "NFKD", then throw
1955 * a RangeError Exception.
1957 ObjectDefineProperty($String.prototype, 'normalize', {
1958 value: function(that) {
1959 if (%_IsConstructCall()) {
1960 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1963 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
1965 var form = $String(%_Arguments(0) || 'NFC');
1967 var normalizationForm = NORMALIZATION_FORMS.indexOf(form);
1968 if (normalizationForm === -1) {
1969 throw new $RangeError('The normalization form should be one of '
1970 + NORMALIZATION_FORMS.join(', ') + '.');
1973 return %StringNormalize(this, normalizationForm);
1979 %FunctionSetName($String.prototype.normalize, 'normalize');
1980 %FunctionRemovePrototype($String.prototype.normalize);
1981 %SetNativeFlag($String.prototype.normalize);
1985 * Formats a Number object (this) using locale and options values.
1986 * If locale or options are omitted, defaults are used.
1988 ObjectDefineProperty($Number.prototype, 'toLocaleString', {
1990 if (%_IsConstructCall()) {
1991 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1994 if (!(this instanceof $Number) && typeof(this) !== 'number') {
1995 throw new $TypeError('Method invoked on an object that is not Number.');
1998 var locales = %_Arguments(0);
1999 var options = %_Arguments(1);
2000 var numberFormat = cachedOrNewService('numberformat', locales, options);
2001 return formatNumber(numberFormat, this);
2007 %FunctionSetName($Number.prototype.toLocaleString, 'toLocaleString');
2008 %FunctionRemovePrototype($Number.prototype.toLocaleString);
2009 %SetNativeFlag($Number.prototype.toLocaleString);
2013 * Returns actual formatted date or fails if date parameter is invalid.
2015 function toLocaleDateTime(date, locales, options, required, defaults, service) {
2016 if (!(date instanceof $Date)) {
2017 throw new $TypeError('Method invoked on an object that is not Date.');
2021 return 'Invalid Date';
2024 var internalOptions = toDateTimeOptions(options, required, defaults);
2027 cachedOrNewService(service, locales, options, internalOptions);
2029 return formatDate(dateFormat, date);
2034 * Formats a Date object (this) using locale and options values.
2035 * If locale or options are omitted, defaults are used - both date and time are
2036 * present in the output.
2038 ObjectDefineProperty($Date.prototype, 'toLocaleString', {
2040 if (%_IsConstructCall()) {
2041 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2044 var locales = %_Arguments(0);
2045 var options = %_Arguments(1);
2046 return toLocaleDateTime(
2047 this, locales, options, 'any', 'all', 'dateformatall');
2053 %FunctionSetName($Date.prototype.toLocaleString, 'toLocaleString');
2054 %FunctionRemovePrototype($Date.prototype.toLocaleString);
2055 %SetNativeFlag($Date.prototype.toLocaleString);
2059 * Formats a Date object (this) using locale and options values.
2060 * If locale or options are omitted, defaults are used - only date is present
2063 ObjectDefineProperty($Date.prototype, 'toLocaleDateString', {
2065 if (%_IsConstructCall()) {
2066 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2069 var locales = %_Arguments(0);
2070 var options = %_Arguments(1);
2071 return toLocaleDateTime(
2072 this, locales, options, 'date', 'date', 'dateformatdate');
2078 %FunctionSetName($Date.prototype.toLocaleDateString, 'toLocaleDateString');
2079 %FunctionRemovePrototype($Date.prototype.toLocaleDateString);
2080 %SetNativeFlag($Date.prototype.toLocaleDateString);
2084 * Formats a Date object (this) using locale and options values.
2085 * If locale or options are omitted, defaults are used - only time is present
2088 ObjectDefineProperty($Date.prototype, 'toLocaleTimeString', {
2090 if (%_IsConstructCall()) {
2091 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2094 var locales = %_Arguments(0);
2095 var options = %_Arguments(1);
2096 return toLocaleDateTime(
2097 this, locales, options, 'time', 'time', 'dateformattime');
2103 %FunctionSetName($Date.prototype.toLocaleTimeString, 'toLocaleTimeString');
2104 %FunctionRemovePrototype($Date.prototype.toLocaleTimeString);
2105 %SetNativeFlag($Date.prototype.toLocaleTimeString);