1 // Copyright 2013 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are
6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above
9 // copyright notice, this list of conditions and the following
10 // disclaimer in the documentation and/or other materials provided
11 // with the distribution.
12 // * Neither the name of Google Inc. nor the names of its
13 // contributors may be used to endorse or promote products derived
14 // from this software without specific prior written permission.
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 // limitations under the License.
29 // ECMAScript 402 API implementation.
32 * Intl object is a single object that has some named properties,
33 * all of which are constructors.
35 $Object.defineProperty(global, "Intl", { enumerable: false, value: (function() {
41 var undefined = global.undefined;
43 var AVAILABLE_SERVICES = ['collator',
48 var NORMALIZATION_FORMS = ['NFC',
54 * Caches available locales for each service.
56 var AVAILABLE_LOCALES = {
57 'collator': undefined,
58 'numberformat': undefined,
59 'dateformat': undefined,
60 'breakiterator': undefined
64 * Caches default ICU locale.
66 var DEFAULT_ICU_LOCALE = undefined;
69 * Unicode extension regular expression.
71 var UNICODE_EXTENSION_RE = undefined;
73 function GetUnicodeExtensionRE() {
74 if (UNICODE_EXTENSION_RE === undefined) {
75 UNICODE_EXTENSION_RE = new $RegExp('-u(-[a-z0-9]{2,8})+', 'g');
77 return UNICODE_EXTENSION_RE;
81 * Matches any Unicode extension.
83 var ANY_EXTENSION_RE = undefined;
85 function GetAnyExtensionRE() {
86 if (ANY_EXTENSION_RE === undefined) {
87 ANY_EXTENSION_RE = new $RegExp('-[a-z0-9]{1}-.*', 'g');
89 return ANY_EXTENSION_RE;
93 * Replace quoted text (single quote, anything but the quote and quote again).
95 var QUOTED_STRING_RE = undefined;
97 function GetQuotedStringRE() {
98 if (QUOTED_STRING_RE === undefined) {
99 QUOTED_STRING_RE = new $RegExp("'[^']+'", 'g');
101 return QUOTED_STRING_RE;
105 * Matches valid service name.
107 var SERVICE_RE = undefined;
109 function GetServiceRE() {
110 if (SERVICE_RE === undefined) {
112 new $RegExp('^(collator|numberformat|dateformat|breakiterator)$');
118 * Validates a language tag against bcp47 spec.
119 * Actual value is assigned on first run.
121 var LANGUAGE_TAG_RE = undefined;
123 function GetLanguageTagRE() {
124 if (LANGUAGE_TAG_RE === undefined) {
125 BuildLanguageTagREs();
127 return LANGUAGE_TAG_RE;
131 * Helps find duplicate variants in the language tag.
133 var LANGUAGE_VARIANT_RE = undefined;
135 function GetLanguageVariantRE() {
136 if (LANGUAGE_VARIANT_RE === undefined) {
137 BuildLanguageTagREs();
139 return LANGUAGE_VARIANT_RE;
143 * Helps find duplicate singletons in the language tag.
145 var LANGUAGE_SINGLETON_RE = undefined;
147 function GetLanguageSingletonRE() {
148 if (LANGUAGE_SINGLETON_RE === undefined) {
149 BuildLanguageTagREs();
151 return LANGUAGE_SINGLETON_RE;
155 * Matches valid IANA time zone names.
157 var TIMEZONE_NAME_CHECK_RE = undefined;
159 function GetTimezoneNameCheckRE() {
160 if (TIMEZONE_NAME_CHECK_RE === undefined) {
161 TIMEZONE_NAME_CHECK_RE =
162 new $RegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$');
164 return TIMEZONE_NAME_CHECK_RE;
168 * Maps ICU calendar names into LDML type.
170 var ICU_CALENDAR_MAP = {
171 'gregorian': 'gregory',
172 'japanese': 'japanese',
173 'buddhist': 'buddhist',
175 'persian': 'persian',
176 'islamic-civil': 'islamicc',
177 'islamic': 'islamic',
179 'chinese': 'chinese',
182 'ethiopic': 'ethiopic',
183 'ethiopic-amete-alem': 'ethioaa'
187 * Map of Unicode extensions to option properties, and their values and types,
190 var COLLATOR_KEY_MAP = {
191 'kn': {'property': 'numeric', 'type': 'boolean'},
192 'kf': {'property': 'caseFirst', 'type': 'string',
193 'values': ['false', 'lower', 'upper']}
197 * Map of Unicode extensions to option properties, and their values and types,
198 * for a number format.
200 var NUMBER_FORMAT_KEY_MAP = {
201 'nu': {'property': undefined, 'type': 'string'}
205 * Map of Unicode extensions to option properties, and their values and types,
206 * for a date/time format.
208 var DATETIME_FORMAT_KEY_MAP = {
209 'ca': {'property': undefined, 'type': 'string'},
210 'nu': {'property': undefined, 'type': 'string'}
214 * Allowed -u-co- values. List taken from:
215 * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml
217 var ALLOWED_CO_VALUES = [
218 'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic',
219 'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin'
223 * Error message for when function object is created with new and it's not
226 var ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR =
227 'Function object that\'s not a constructor was created with new';
231 * Adds bound method to the prototype of the given object.
233 function addBoundMethod(obj, methodName, implementation, length) {
235 if (!%IsInitializedIntlObject(this)) {
236 throw new $TypeError('Method ' + methodName + ' called on a ' +
237 'non-object or on a wrong type of object.');
239 var internalName = '__bound' + methodName + '__';
240 if (this[internalName] === undefined) {
243 if (length === undefined || length === 2) {
244 boundMethod = function(x, y) {
245 if (%_IsConstructCall()) {
246 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
248 return implementation(that, x, y);
250 } else if (length === 1) {
251 boundMethod = function(x) {
252 if (%_IsConstructCall()) {
253 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
255 return implementation(that, x);
258 boundMethod = function() {
259 if (%_IsConstructCall()) {
260 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
262 // DateTimeFormat.format needs to be 0 arg method, but can stil
263 // receive optional dateValue param. If one was provided, pass it
265 if (%_ArgumentsLength() > 0) {
266 return implementation(that, %_Arguments(0));
268 return implementation(that);
272 %FunctionSetName(boundMethod, internalName);
273 %FunctionRemovePrototype(boundMethod);
274 %SetNativeFlag(boundMethod);
275 this[internalName] = boundMethod;
277 return this[internalName];
280 %FunctionSetName(getter, methodName);
281 %FunctionRemovePrototype(getter);
282 %SetNativeFlag(getter);
284 $Object.defineProperty(obj.prototype, methodName, {
293 * Returns an intersection of locales and service supported locales.
294 * Parameter locales is treated as a priority list.
296 function supportedLocalesOf(service, locales, options) {
297 if (IS_NULL(service.match(GetServiceRE()))) {
298 throw new $Error('Internal error, wrong service type: ' + service);
301 // Provide defaults if matcher was not specified.
302 if (options === undefined) {
305 options = toObject(options);
308 var matcher = options.localeMatcher;
309 if (matcher !== undefined) {
310 matcher = $String(matcher);
311 if (matcher !== 'lookup' && matcher !== 'best fit') {
312 throw new $RangeError('Illegal value for localeMatcher:' + matcher);
315 matcher = 'best fit';
318 var requestedLocales = initializeLocaleList(locales);
320 // Cache these, they don't ever change per service.
321 if (AVAILABLE_LOCALES[service] === undefined) {
322 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
325 // Use either best fit or lookup algorithm to match locales.
326 if (matcher === 'best fit') {
327 return initializeLocaleList(bestFitSupportedLocalesOf(
328 requestedLocales, AVAILABLE_LOCALES[service]));
331 return initializeLocaleList(lookupSupportedLocalesOf(
332 requestedLocales, AVAILABLE_LOCALES[service]));
337 * Returns the subset of the provided BCP 47 language priority list for which
338 * this service has a matching locale when using the BCP 47 Lookup algorithm.
339 * Locales appear in the same order in the returned list as in the input list.
341 function lookupSupportedLocalesOf(requestedLocales, availableLocales) {
342 var matchedLocales = [];
343 for (var i = 0; i < requestedLocales.length; ++i) {
344 // Remove -u- extension.
345 var locale = requestedLocales[i].replace(GetUnicodeExtensionRE(), '');
347 if (availableLocales[locale] !== undefined) {
348 // Push requested locale not the resolved one.
349 matchedLocales.push(requestedLocales[i]);
352 // Truncate locale if possible, if not break.
353 var pos = locale.lastIndexOf('-');
357 locale = locale.substring(0, pos);
361 return matchedLocales;
366 * Returns the subset of the provided BCP 47 language priority list for which
367 * this service has a matching locale when using the implementation
368 * dependent algorithm.
369 * Locales appear in the same order in the returned list as in the input list.
371 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) {
372 // TODO(cira): implement better best fit algorithm.
373 return lookupSupportedLocalesOf(requestedLocales, availableLocales);
378 * Returns a getOption function that extracts property value for given
379 * options object. If property is missing it returns defaultValue. If value
380 * is out of range for that property it throws RangeError.
382 function getGetOption(options, caller) {
383 if (options === undefined) {
384 throw new $Error('Internal ' + caller + ' error. ' +
385 'Default options are missing.');
388 var getOption = function getOption(property, type, values, defaultValue) {
389 if (options[property] !== undefined) {
390 var value = options[property];
393 value = $Boolean(value);
396 value = $String(value);
399 value = $Number(value);
402 throw new $Error('Internal error. Wrong value type.');
404 if (values !== undefined && values.indexOf(value) === -1) {
405 throw new $RangeError('Value ' + value + ' out of range for ' + caller +
406 ' options property ' + property);
420 * Compares a BCP 47 language priority list requestedLocales against the locales
421 * in availableLocales and determines the best available language to meet the
422 * request. Two algorithms are available to match the locales: the Lookup
423 * algorithm described in RFC 4647 section 3.4, and an implementation dependent
424 * best-fit algorithm. Independent of the locale matching algorithm, options
425 * specified through Unicode locale extension sequences are negotiated
426 * separately, taking the caller's relevant extension keys and locale data as
427 * well as client-provided options into consideration. Returns an object with
428 * a locale property whose value is the language tag of the selected locale,
429 * and properties for each key in relevantExtensionKeys providing the selected
430 * value for that key.
432 function resolveLocale(service, requestedLocales, options) {
433 requestedLocales = initializeLocaleList(requestedLocales);
435 var getOption = getGetOption(options, service);
436 var matcher = getOption('localeMatcher', 'string',
437 ['lookup', 'best fit'], 'best fit');
439 if (matcher === 'lookup') {
440 resolved = lookupMatcher(service, requestedLocales);
442 resolved = bestFitMatcher(service, requestedLocales);
450 * Returns best matched supported locale and extension info using basic
453 function lookupMatcher(service, requestedLocales) {
454 if (IS_NULL(service.match(GetServiceRE()))) {
455 throw new $Error('Internal error, wrong service type: ' + service);
458 // Cache these, they don't ever change per service.
459 if (AVAILABLE_LOCALES[service] === undefined) {
460 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
463 for (var i = 0; i < requestedLocales.length; ++i) {
464 // Remove all extensions.
465 var locale = requestedLocales[i].replace(GetAnyExtensionRE(), '');
467 if (AVAILABLE_LOCALES[service][locale] !== undefined) {
468 // Return the resolved locale and extension.
469 var extensionMatch = requestedLocales[i].match(GetUnicodeExtensionRE());
470 var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0];
471 return {'locale': locale, 'extension': extension, 'position': i};
473 // Truncate locale if possible.
474 var pos = locale.lastIndexOf('-');
478 locale = locale.substring(0, pos);
482 // Didn't find a match, return default.
483 if (DEFAULT_ICU_LOCALE === undefined) {
484 DEFAULT_ICU_LOCALE = %GetDefaultICULocale();
487 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1};
492 * Returns best matched supported locale and extension info using
493 * implementation dependend algorithm.
495 function bestFitMatcher(service, requestedLocales) {
496 // TODO(cira): implement better best fit algorithm.
497 return lookupMatcher(service, requestedLocales);
502 * Parses Unicode extension into key - value map.
503 * Returns empty object if the extension string is invalid.
504 * We are not concerned with the validity of the values at this point.
506 function parseExtension(extension) {
507 var extensionSplit = extension.split('-');
509 // Assume ['', 'u', ...] input, but don't throw.
510 if (extensionSplit.length <= 2 ||
511 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
515 // Key is {2}alphanum, value is {3,8}alphanum.
516 // Some keys may not have explicit values (booleans).
517 var extensionMap = {};
518 var previousKey = undefined;
519 for (var i = 2; i < extensionSplit.length; ++i) {
520 var length = extensionSplit[i].length;
521 var element = extensionSplit[i];
523 extensionMap[element] = undefined;
524 previousKey = element;
525 } else if (length >= 3 && length <=8 && previousKey !== undefined) {
526 extensionMap[previousKey] = element;
527 previousKey = undefined;
529 // There is a value that's too long, or that doesn't have a key.
539 * Converts parameter to an Object if possible.
541 function toObject(value) {
542 if (IS_NULL_OR_UNDEFINED(value)) {
543 throw new $TypeError('Value cannot be converted to an Object.');
546 return $Object(value);
551 * Populates internalOptions object with boolean key-value pairs
552 * from extensionMap and options.
553 * Returns filtered extension (number and date format constructors use
554 * Unicode extensions for passing parameters to ICU).
555 * It's used for extension-option pairs only, e.g. kn-normalization, but not
556 * for 'sensitivity' since it doesn't have extension equivalent.
557 * Extensions like nu and ca don't have options equivalent, so we place
558 * undefined in the map.property to denote that.
560 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
563 var updateExtension = function updateExtension(key, value) {
564 return '-' + key + '-' + $String(value);
567 var updateProperty = function updateProperty(property, type, value) {
568 if (type === 'boolean' && (typeof value === 'string')) {
569 value = (value === 'true') ? true : false;
572 if (property !== undefined) {
573 defineWEProperty(outOptions, property, value);
577 for (var key in keyValues) {
578 if (keyValues.hasOwnProperty(key)) {
579 var value = undefined;
580 var map = keyValues[key];
581 if (map.property !== undefined) {
582 // This may return true if user specifies numeric: 'false', since
583 // Boolean('nonempty') === true.
584 value = getOption(map.property, map.type, map.values);
586 if (value !== undefined) {
587 updateProperty(map.property, map.type, value);
588 extension += updateExtension(key, value);
591 // User options didn't have it, check Unicode extension.
592 // Here we want to convert strings 'true', 'false' into proper Boolean
593 // values (not a user error).
594 if (extensionMap.hasOwnProperty(key)) {
595 value = extensionMap[key];
596 if (value !== undefined) {
597 updateProperty(map.property, map.type, value);
598 extension += updateExtension(key, value);
599 } else if (map.type === 'boolean') {
600 // Boolean keys are allowed not to have values in Unicode extension.
601 // Those default to true.
602 updateProperty(map.property, map.type, true);
603 extension += updateExtension(key, true);
609 return extension === ''? '' : '-u' + extension;
614 * Converts all OwnProperties into
615 * configurable: false, writable: false, enumerable: true.
617 function freezeArray(array) {
618 array.forEach(function(element, index) {
619 $Object.defineProperty(array, index, {value: element,
625 $Object.defineProperty(array, 'length', {value: array.length,
633 * It's sometimes desireable to leave user requested locale instead of ICU
634 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
635 * one, if that was what user requested).
636 * This function returns user specified tag if its maximized form matches ICU
637 * resolved locale. If not we return ICU result.
639 function getOptimalLanguageTag(original, resolved) {
640 // Returns Array<Object>, where each object has maximized and base properties.
641 // Maximized: zh -> zh-Hans-CN
642 // Base: zh-CN-u-ca-gregory -> zh-CN
643 // Take care of grandfathered or simple cases.
644 if (original === resolved) {
648 var locales = %GetLanguageTagVariants([original, resolved]);
649 if (locales[0].maximized !== locales[1].maximized) {
653 // Preserve extensions of resolved locale, but swap base tags with original.
654 var resolvedBase = new $RegExp('^' + locales[1].base);
655 return resolved.replace(resolvedBase, locales[0].base);
660 * Returns an Object that contains all of supported locales for a given
662 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ
663 * that is supported. This is required by the spec.
665 function getAvailableLocalesOf(service) {
666 var available = %AvailableLocalesOf(service);
668 for (var i in available) {
669 if (available.hasOwnProperty(i)) {
670 var parts = i.match(/^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/);
671 if (parts !== null) {
672 // Build xx-ZZ. We don't care about the actual value,
673 // as long it's not undefined.
674 available[parts[1] + '-' + parts[3]] = null;
684 * Defines a property and sets writable and enumerable to true.
685 * Configurable is false by default.
687 function defineWEProperty(object, property, value) {
688 $Object.defineProperty(object, property,
689 {value: value, writable: true, enumerable: true});
694 * Adds property to an object if the value is not undefined.
695 * Sets configurable descriptor to false.
697 function addWEPropertyIfDefined(object, property, value) {
698 if (value !== undefined) {
699 defineWEProperty(object, property, value);
705 * Defines a property and sets writable, enumerable and configurable to true.
707 function defineWECProperty(object, property, value) {
708 $Object.defineProperty(object, property,
712 configurable: true});
717 * Adds property to an object if the value is not undefined.
718 * Sets all descriptors to true.
720 function addWECPropertyIfDefined(object, property, value) {
721 if (value !== undefined) {
722 defineWECProperty(object, property, value);
728 * Returns titlecased word, aMeRricA -> America.
730 function toTitleCaseWord(word) {
731 return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase();
735 * Canonicalizes the language tag, or throws in case the tag is invalid.
737 function canonicalizeLanguageTag(localeID) {
738 // null is typeof 'object' so we have to do extra check.
739 if (typeof localeID !== 'string' && typeof localeID !== 'object' ||
741 throw new $TypeError('Language ID should be string or object.');
744 var localeString = $String(localeID);
746 if (isValidLanguageTag(localeString) === false) {
747 throw new $RangeError('Invalid language tag: ' + localeString);
750 // This call will strip -kn but not -kn-true extensions.
751 // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265.
752 // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after
753 // upgrade to ICU 4.9.
754 var tag = %CanonicalizeLanguageTag(localeString);
755 if (tag === 'invalid-tag') {
756 throw new $RangeError('Invalid language tag: ' + localeString);
764 * Returns an array where all locales are canonicalized and duplicates removed.
765 * Throws on locales that are not well formed BCP47 tags.
767 function initializeLocaleList(locales) {
769 if (locales === undefined) {
770 // Constructor is called without arguments.
773 // We allow single string localeID.
774 if (typeof locales === 'string') {
775 seen.push(canonicalizeLanguageTag(locales));
776 return freezeArray(seen);
779 var o = toObject(locales);
780 // Converts it to UInt32 (>>> is shr on 32bit integers).
781 var len = o.length >>> 0;
783 for (var k = 0; k < len; k++) {
787 var tag = canonicalizeLanguageTag(value);
789 if (seen.indexOf(tag) === -1) {
796 return freezeArray(seen);
801 * Validates the language tag. Section 2.2.9 of the bcp47 spec
802 * defines a valid tag.
804 * ICU is too permissible and lets invalid tags, like
805 * hant-cmn-cn, through.
807 * Returns false if the language tag is invalid.
809 function isValidLanguageTag(locale) {
810 // Check if it's well-formed, including grandfadered tags.
811 if (GetLanguageTagRE().test(locale) === false) {
815 // Just return if it's a x- form. It's all private.
816 if (locale.indexOf('x-') === 0) {
820 // Check if there are any duplicate variants or singletons (extensions).
822 // Remove private use section.
823 locale = locale.split(/-x-/)[0];
825 // Skip language since it can match variant regex, so we start from 1.
826 // We are matching i-klingon here, but that's ok, since i-klingon-klingon
827 // is not valid and would fail LANGUAGE_TAG_RE test.
830 var parts = locale.split(/-/);
831 for (var i = 1; i < parts.length; i++) {
832 var value = parts[i];
833 if (GetLanguageVariantRE().test(value) === true && extensions.length === 0) {
834 if (variants.indexOf(value) === -1) {
835 variants.push(value);
841 if (GetLanguageSingletonRE().test(value) === true) {
842 if (extensions.indexOf(value) === -1) {
843 extensions.push(value);
855 * Builds a regular expresion that validates the language tag
856 * against bcp47 spec.
857 * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF.
858 * Runs on load and initializes the global REs.
860 function BuildLanguageTagREs() {
861 var alpha = '[a-zA-Z]';
863 var alphanum = '(' + alpha + '|' + digit + ')';
864 var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' +
865 'zh-min|zh-min-nan|zh-xiang)';
866 var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' +
867 'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' +
868 'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)';
869 var grandfathered = '(' + irregular + '|' + regular + ')';
870 var privateUse = '(x(-' + alphanum + '{1,8})+)';
872 var singleton = '(' + digit + '|[A-WY-Za-wy-z])';
873 LANGUAGE_SINGLETON_RE = new $RegExp('^' + singleton + '$', 'i');
875 var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)';
877 var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))';
878 LANGUAGE_VARIANT_RE = new $RegExp('^' + variant + '$', 'i');
880 var region = '(' + alpha + '{2}|' + digit + '{3})';
881 var script = '(' + alpha + '{4})';
882 var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})';
883 var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' +
885 var langTag = language + '(-' + script + ')?(-' + region + ')?(-' +
886 variant + ')*(-' + extension + ')*(-' + privateUse + ')?';
889 '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$';
890 LANGUAGE_TAG_RE = new $RegExp(languageTag, 'i');
894 * Initializes the given object so it's a valid Collator instance.
895 * Useful for subclassing.
897 function initializeCollator(collator, locales, options) {
898 if (%IsInitializedIntlObject(collator)) {
899 throw new $TypeError('Trying to re-initialize Collator object.');
902 if (options === undefined) {
906 var getOption = getGetOption(options, 'collator');
908 var internalOptions = {};
910 defineWEProperty(internalOptions, 'usage', getOption(
911 'usage', 'string', ['sort', 'search'], 'sort'));
913 var sensitivity = getOption('sensitivity', 'string',
914 ['base', 'accent', 'case', 'variant']);
915 if (sensitivity === undefined && internalOptions.usage === 'sort') {
916 sensitivity = 'variant';
918 defineWEProperty(internalOptions, 'sensitivity', sensitivity);
920 defineWEProperty(internalOptions, 'ignorePunctuation', getOption(
921 'ignorePunctuation', 'boolean', undefined, false));
923 var locale = resolveLocale('collator', locales, options);
925 // ICU can't take kb, kc... parameters through localeID, so we need to pass
927 // One exception is -co- which has to be part of the extension, but only for
928 // usage: sort, and its value can't be 'standard' or 'search'.
929 var extensionMap = parseExtension(locale.extension);
931 options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions);
933 var collation = 'default';
935 if (extensionMap.hasOwnProperty('co') && internalOptions.usage === 'sort') {
936 if (ALLOWED_CO_VALUES.indexOf(extensionMap.co) !== -1) {
937 extension = '-u-co-' + extensionMap.co;
938 // ICU can't tell us what the collation is, so save user's input.
939 collation = extensionMap.co;
941 } else if (internalOptions.usage === 'search') {
942 extension = '-u-co-search';
944 defineWEProperty(internalOptions, 'collation', collation);
946 var requestedLocale = locale.locale + extension;
948 // We define all properties C++ code may produce, to prevent security
949 // problems. If malicious user decides to redefine Object.prototype.locale
950 // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us").
951 // Object.defineProperties will either succeed defining or throw an error.
952 var resolved = $Object.defineProperties({}, {
953 caseFirst: {writable: true},
954 collation: {value: internalOptions.collation, writable: true},
955 ignorePunctuation: {writable: true},
956 locale: {writable: true},
957 numeric: {writable: true},
958 requestedLocale: {value: requestedLocale, writable: true},
959 sensitivity: {writable: true},
960 strength: {writable: true},
961 usage: {value: internalOptions.usage, writable: true}
964 var internalCollator = %CreateCollator(requestedLocale,
968 // Writable, configurable and enumerable are set to false by default.
969 %MarkAsInitializedIntlObjectOfType(collator, 'collator', internalCollator);
970 $Object.defineProperty(collator, 'resolved', {value: resolved});
977 * Constructs Intl.Collator object given optional locales and options
982 %SetProperty(Intl, 'Collator', function() {
983 var locales = %_Arguments(0);
984 var options = %_Arguments(1);
986 if (!this || this === Intl) {
987 // Constructor is called as a function.
988 return new Intl.Collator(locales, options);
991 return initializeCollator(toObject(this), locales, options);
998 * Collator resolvedOptions method.
1000 %SetProperty(Intl.Collator.prototype, 'resolvedOptions', function() {
1001 if (%_IsConstructCall()) {
1002 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1005 if (!%IsInitializedIntlObjectOfType(this, 'collator')) {
1006 throw new $TypeError('resolvedOptions method called on a non-object ' +
1007 'or on a object that is not Intl.Collator.');
1011 var locale = getOptimalLanguageTag(coll.resolved.requestedLocale,
1012 coll.resolved.locale);
1016 usage: coll.resolved.usage,
1017 sensitivity: coll.resolved.sensitivity,
1018 ignorePunctuation: coll.resolved.ignorePunctuation,
1019 numeric: coll.resolved.numeric,
1020 caseFirst: coll.resolved.caseFirst,
1021 collation: coll.resolved.collation
1026 %FunctionSetName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions');
1027 %FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions);
1028 %SetNativeFlag(Intl.Collator.prototype.resolvedOptions);
1032 * Returns the subset of the given locale list for which this locale list
1033 * has a matching (possibly fallback) locale. Locales appear in the same
1034 * order in the returned list as in the input list.
1035 * Options are optional parameter.
1037 %SetProperty(Intl.Collator, 'supportedLocalesOf', function(locales) {
1038 if (%_IsConstructCall()) {
1039 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1042 return supportedLocalesOf('collator', locales, %_Arguments(1));
1046 %FunctionSetName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf');
1047 %FunctionRemovePrototype(Intl.Collator.supportedLocalesOf);
1048 %SetNativeFlag(Intl.Collator.supportedLocalesOf);
1052 * When the compare method is called with two arguments x and y, it returns a
1053 * Number other than NaN that represents the result of a locale-sensitive
1054 * String comparison of x with y.
1055 * The result is intended to order String values in the sort order specified
1056 * by the effective locale and collation options computed during construction
1057 * of this Collator object, and will be negative, zero, or positive, depending
1058 * on whether x comes before y in the sort order, the Strings are equal under
1059 * the sort order, or x comes after y in the sort order, respectively.
1061 function compare(collator, x, y) {
1062 return %InternalCompare(%GetImplFromInitializedIntlObject(collator),
1063 $String(x), $String(y));
1067 addBoundMethod(Intl.Collator, 'compare', compare, 2);
1070 * Verifies that the input is a well-formed ISO 4217 currency code.
1071 * Don't uppercase to test. It could convert invalid code into a valid one.
1072 * For example \u00DFP (Eszett+P) becomes SSP.
1074 function isWellFormedCurrencyCode(currency) {
1075 return typeof currency == "string" &&
1076 currency.length == 3 &&
1077 currency.match(/[^A-Za-z]/) == null;
1082 * Returns the valid digit count for a property, or throws RangeError on
1083 * a value out of the range.
1085 function getNumberOption(options, property, min, max, fallback) {
1086 var value = options[property];
1087 if (value !== undefined) {
1088 value = $Number(value);
1089 if ($isNaN(value) || value < min || value > max) {
1090 throw new $RangeError(property + ' value is out of range.');
1092 return $floor(value);
1100 * Initializes the given object so it's a valid NumberFormat instance.
1101 * Useful for subclassing.
1103 function initializeNumberFormat(numberFormat, locales, options) {
1104 if (%IsInitializedIntlObject(numberFormat)) {
1105 throw new $TypeError('Trying to re-initialize NumberFormat object.');
1108 if (options === undefined) {
1112 var getOption = getGetOption(options, 'numberformat');
1114 var locale = resolveLocale('numberformat', locales, options);
1116 var internalOptions = {};
1117 defineWEProperty(internalOptions, 'style', getOption(
1118 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal'));
1120 var currency = getOption('currency', 'string');
1121 if (currency !== undefined && !isWellFormedCurrencyCode(currency)) {
1122 throw new $RangeError('Invalid currency code: ' + currency);
1125 if (internalOptions.style === 'currency' && currency === undefined) {
1126 throw new $TypeError('Currency code is required with currency style.');
1129 var currencyDisplay = getOption(
1130 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
1131 if (internalOptions.style === 'currency') {
1132 defineWEProperty(internalOptions, 'currency', currency.toUpperCase());
1133 defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay);
1137 var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
1138 defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid);
1140 var mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0);
1141 defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd);
1143 var mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, 3);
1144 defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd);
1146 var mnsd = options['minimumSignificantDigits'];
1147 var mxsd = options['maximumSignificantDigits'];
1148 if (mnsd !== undefined || mxsd !== undefined) {
1149 mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0);
1150 defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd);
1152 mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21);
1153 defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd);
1157 defineWEProperty(internalOptions, 'useGrouping', getOption(
1158 'useGrouping', 'boolean', undefined, true));
1160 // ICU prefers options to be passed using -u- extension key/values for
1161 // number format, so we need to build that.
1162 var extensionMap = parseExtension(locale.extension);
1163 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP,
1164 getOption, internalOptions);
1166 var requestedLocale = locale.locale + extension;
1167 var resolved = $Object.defineProperties({}, {
1168 currency: {writable: true},
1169 currencyDisplay: {writable: true},
1170 locale: {writable: true},
1171 maximumFractionDigits: {writable: true},
1172 minimumFractionDigits: {writable: true},
1173 minimumIntegerDigits: {writable: true},
1174 numberingSystem: {writable: true},
1175 requestedLocale: {value: requestedLocale, writable: true},
1176 style: {value: internalOptions.style, writable: true},
1177 useGrouping: {writable: true}
1179 if (internalOptions.hasOwnProperty('minimumSignificantDigits')) {
1180 defineWEProperty(resolved, 'minimumSignificantDigits', undefined);
1182 if (internalOptions.hasOwnProperty('maximumSignificantDigits')) {
1183 defineWEProperty(resolved, 'maximumSignificantDigits', undefined);
1185 var formatter = %CreateNumberFormat(requestedLocale,
1189 // We can't get information about number or currency style from ICU, so we
1190 // assume user request was fulfilled.
1191 if (internalOptions.style === 'currency') {
1192 $Object.defineProperty(resolved, 'currencyDisplay', {value: currencyDisplay,
1196 %MarkAsInitializedIntlObjectOfType(numberFormat, 'numberformat', formatter);
1197 $Object.defineProperty(numberFormat, 'resolved', {value: resolved});
1199 return numberFormat;
1204 * Constructs Intl.NumberFormat object given optional locales and options
1209 %SetProperty(Intl, 'NumberFormat', function() {
1210 var locales = %_Arguments(0);
1211 var options = %_Arguments(1);
1213 if (!this || this === Intl) {
1214 // Constructor is called as a function.
1215 return new Intl.NumberFormat(locales, options);
1218 return initializeNumberFormat(toObject(this), locales, options);
1225 * NumberFormat resolvedOptions method.
1227 %SetProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() {
1228 if (%_IsConstructCall()) {
1229 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1232 if (!%IsInitializedIntlObjectOfType(this, 'numberformat')) {
1233 throw new $TypeError('resolvedOptions method called on a non-object' +
1234 ' or on a object that is not Intl.NumberFormat.');
1238 var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1239 format.resolved.locale);
1243 numberingSystem: format.resolved.numberingSystem,
1244 style: format.resolved.style,
1245 useGrouping: format.resolved.useGrouping,
1246 minimumIntegerDigits: format.resolved.minimumIntegerDigits,
1247 minimumFractionDigits: format.resolved.minimumFractionDigits,
1248 maximumFractionDigits: format.resolved.maximumFractionDigits,
1251 if (result.style === 'currency') {
1252 defineWECProperty(result, 'currency', format.resolved.currency);
1253 defineWECProperty(result, 'currencyDisplay',
1254 format.resolved.currencyDisplay);
1257 if (format.resolved.hasOwnProperty('minimumSignificantDigits')) {
1258 defineWECProperty(result, 'minimumSignificantDigits',
1259 format.resolved.minimumSignificantDigits);
1262 if (format.resolved.hasOwnProperty('maximumSignificantDigits')) {
1263 defineWECProperty(result, 'maximumSignificantDigits',
1264 format.resolved.maximumSignificantDigits);
1271 %FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions,
1273 %FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions);
1274 %SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions);
1278 * Returns the subset of the given locale list for which this locale list
1279 * has a matching (possibly fallback) locale. Locales appear in the same
1280 * order in the returned list as in the input list.
1281 * Options are optional parameter.
1283 %SetProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) {
1284 if (%_IsConstructCall()) {
1285 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1288 return supportedLocalesOf('numberformat', locales, %_Arguments(1));
1292 %FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf');
1293 %FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf);
1294 %SetNativeFlag(Intl.NumberFormat.supportedLocalesOf);
1298 * Returns a String value representing the result of calling ToNumber(value)
1299 * according to the effective locale and the formatting options of this
1302 function formatNumber(formatter, value) {
1303 // Spec treats -0 and +0 as 0.
1304 var number = $Number(value) + 0;
1306 return %InternalNumberFormat(%GetImplFromInitializedIntlObject(formatter),
1312 * Returns a Number that represents string value that was passed in.
1314 function parseNumber(formatter, value) {
1315 return %InternalNumberParse(%GetImplFromInitializedIntlObject(formatter),
1320 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1);
1321 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1);
1324 * Returns a string that matches LDML representation of the options object.
1326 function toLDMLString(options) {
1327 var getOption = getGetOption(options, 'dateformat');
1329 var ldmlString = '';
1331 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']);
1332 ldmlString += appendToLDMLString(
1333 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'});
1335 option = getOption('era', 'string', ['narrow', 'short', 'long']);
1336 ldmlString += appendToLDMLString(
1337 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'});
1339 option = getOption('year', 'string', ['2-digit', 'numeric']);
1340 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'});
1342 option = getOption('month', 'string',
1343 ['2-digit', 'numeric', 'narrow', 'short', 'long']);
1344 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M',
1345 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'});
1347 option = getOption('day', 'string', ['2-digit', 'numeric']);
1348 ldmlString += appendToLDMLString(
1349 option, {'2-digit': 'dd', 'numeric': 'd'});
1351 var hr12 = getOption('hour12', 'boolean');
1352 option = getOption('hour', 'string', ['2-digit', 'numeric']);
1353 if (hr12 === undefined) {
1354 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'});
1355 } else if (hr12 === true) {
1356 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'});
1358 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'});
1361 option = getOption('minute', 'string', ['2-digit', 'numeric']);
1362 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'});
1364 option = getOption('second', 'string', ['2-digit', 'numeric']);
1365 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'});
1367 option = getOption('timeZoneName', 'string', ['short', 'long']);
1368 ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'});
1375 * Returns either LDML equivalent of the current option or empty string.
1377 function appendToLDMLString(option, pairs) {
1378 if (option !== undefined) {
1379 return pairs[option];
1387 * Returns object that matches LDML representation of the date.
1389 function fromLDMLString(ldmlString) {
1390 // First remove '' quoted text, so we lose 'Uhr' strings.
1391 ldmlString = ldmlString.replace(GetQuotedStringRE(), '');
1394 var match = ldmlString.match(/E{3,5}/g);
1395 options = appendToDateTimeObject(
1396 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'});
1398 match = ldmlString.match(/G{3,5}/g);
1399 options = appendToDateTimeObject(
1400 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'});
1402 match = ldmlString.match(/y{1,2}/g);
1403 options = appendToDateTimeObject(
1404 options, 'year', match, {y: 'numeric', yy: '2-digit'});
1406 match = ldmlString.match(/M{1,5}/g);
1407 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit',
1408 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'});
1410 // Sometimes we get L instead of M for month - standalone name.
1411 match = ldmlString.match(/L{1,5}/g);
1412 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit',
1413 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'});
1415 match = ldmlString.match(/d{1,2}/g);
1416 options = appendToDateTimeObject(
1417 options, 'day', match, {d: 'numeric', dd: '2-digit'});
1419 match = ldmlString.match(/h{1,2}/g);
1420 if (match !== null) {
1421 options['hour12'] = true;
1423 options = appendToDateTimeObject(
1424 options, 'hour', match, {h: 'numeric', hh: '2-digit'});
1426 match = ldmlString.match(/H{1,2}/g);
1427 if (match !== null) {
1428 options['hour12'] = false;
1430 options = appendToDateTimeObject(
1431 options, 'hour', match, {H: 'numeric', HH: '2-digit'});
1433 match = ldmlString.match(/m{1,2}/g);
1434 options = appendToDateTimeObject(
1435 options, 'minute', match, {m: 'numeric', mm: '2-digit'});
1437 match = ldmlString.match(/s{1,2}/g);
1438 options = appendToDateTimeObject(
1439 options, 'second', match, {s: 'numeric', ss: '2-digit'});
1441 match = ldmlString.match(/z|zzzz/g);
1442 options = appendToDateTimeObject(
1443 options, 'timeZoneName', match, {z: 'short', zzzz: 'long'});
1449 function appendToDateTimeObject(options, option, match, pairs) {
1450 if (IS_NULL(match)) {
1451 if (!options.hasOwnProperty(option)) {
1452 defineWEProperty(options, option, undefined);
1457 var property = match[0];
1458 defineWEProperty(options, option, pairs[property]);
1465 * Returns options with at least default values in it.
1467 function toDateTimeOptions(options, required, defaults) {
1468 if (options === undefined) {
1471 options = toObject(options);
1474 options = $Object.apply(this, [options]);
1476 var needsDefault = true;
1477 if ((required === 'date' || required === 'any') &&
1478 (options.weekday !== undefined || options.year !== undefined ||
1479 options.month !== undefined || options.day !== undefined)) {
1480 needsDefault = false;
1483 if ((required === 'time' || required === 'any') &&
1484 (options.hour !== undefined || options.minute !== undefined ||
1485 options.second !== undefined)) {
1486 needsDefault = false;
1489 if (needsDefault && (defaults === 'date' || defaults === 'all')) {
1490 $Object.defineProperty(options, 'year', {value: 'numeric',
1493 configurable: true});
1494 $Object.defineProperty(options, 'month', {value: 'numeric',
1497 configurable: true});
1498 $Object.defineProperty(options, 'day', {value: 'numeric',
1501 configurable: true});
1504 if (needsDefault && (defaults === 'time' || defaults === 'all')) {
1505 $Object.defineProperty(options, 'hour', {value: 'numeric',
1508 configurable: true});
1509 $Object.defineProperty(options, 'minute', {value: 'numeric',
1512 configurable: true});
1513 $Object.defineProperty(options, 'second', {value: 'numeric',
1516 configurable: true});
1524 * Initializes the given object so it's a valid DateTimeFormat instance.
1525 * Useful for subclassing.
1527 function initializeDateTimeFormat(dateFormat, locales, options) {
1529 if (%IsInitializedIntlObject(dateFormat)) {
1530 throw new $TypeError('Trying to re-initialize DateTimeFormat object.');
1533 if (options === undefined) {
1537 var locale = resolveLocale('dateformat', locales, options);
1539 options = toDateTimeOptions(options, 'any', 'date');
1541 var getOption = getGetOption(options, 'dateformat');
1543 // We implement only best fit algorithm, but still need to check
1544 // if the formatMatcher values are in range.
1545 var matcher = getOption('formatMatcher', 'string',
1546 ['basic', 'best fit'], 'best fit');
1548 // Build LDML string for the skeleton that we pass to the formatter.
1549 var ldmlString = toLDMLString(options);
1551 // Filter out supported extension keys so we know what to put in resolved
1552 // section later on.
1553 // We need to pass calendar and number system to the method.
1554 var tz = canonicalizeTimeZoneID(options.timeZone);
1556 // ICU prefers options to be passed using -u- extension key/values, so
1557 // we need to build that.
1558 var internalOptions = {};
1559 var extensionMap = parseExtension(locale.extension);
1560 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP,
1561 getOption, internalOptions);
1563 var requestedLocale = locale.locale + extension;
1564 var resolved = $Object.defineProperties({}, {
1565 calendar: {writable: true},
1566 day: {writable: true},
1567 era: {writable: true},
1568 hour12: {writable: true},
1569 hour: {writable: true},
1570 locale: {writable: true},
1571 minute: {writable: true},
1572 month: {writable: true},
1573 numberingSystem: {writable: true},
1574 pattern: {writable: true},
1575 requestedLocale: {value: requestedLocale, writable: true},
1576 second: {writable: true},
1577 timeZone: {writable: true},
1578 timeZoneName: {writable: true},
1579 tz: {value: tz, writable: true},
1580 weekday: {writable: true},
1581 year: {writable: true}
1584 var formatter = %CreateDateTimeFormat(
1585 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved);
1587 if (tz !== undefined && tz !== resolved.timeZone) {
1588 throw new $RangeError('Unsupported time zone specified ' + tz);
1591 %MarkAsInitializedIntlObjectOfType(dateFormat, 'dateformat', formatter);
1592 $Object.defineProperty(dateFormat, 'resolved', {value: resolved});
1599 * Constructs Intl.DateTimeFormat object given optional locales and options
1604 %SetProperty(Intl, 'DateTimeFormat', function() {
1605 var locales = %_Arguments(0);
1606 var options = %_Arguments(1);
1608 if (!this || this === Intl) {
1609 // Constructor is called as a function.
1610 return new Intl.DateTimeFormat(locales, options);
1613 return initializeDateTimeFormat(toObject(this), locales, options);
1620 * DateTimeFormat resolvedOptions method.
1622 %SetProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
1623 if (%_IsConstructCall()) {
1624 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1627 if (!%IsInitializedIntlObjectOfType(this, 'dateformat')) {
1628 throw new $TypeError('resolvedOptions method called on a non-object or ' +
1629 'on a object that is not Intl.DateTimeFormat.');
1633 var fromPattern = fromLDMLString(format.resolved.pattern);
1634 var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar];
1635 if (userCalendar === undefined) {
1636 // Use ICU name if we don't have a match. It shouldn't happen, but
1637 // it would be too strict to throw for this.
1638 userCalendar = format.resolved.calendar;
1641 var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1642 format.resolved.locale);
1646 numberingSystem: format.resolved.numberingSystem,
1647 calendar: userCalendar,
1648 timeZone: format.resolved.timeZone
1651 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName);
1652 addWECPropertyIfDefined(result, 'era', fromPattern.era);
1653 addWECPropertyIfDefined(result, 'year', fromPattern.year);
1654 addWECPropertyIfDefined(result, 'month', fromPattern.month);
1655 addWECPropertyIfDefined(result, 'day', fromPattern.day);
1656 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday);
1657 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12);
1658 addWECPropertyIfDefined(result, 'hour', fromPattern.hour);
1659 addWECPropertyIfDefined(result, 'minute', fromPattern.minute);
1660 addWECPropertyIfDefined(result, 'second', fromPattern.second);
1666 %FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions,
1668 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions);
1669 %SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions);
1673 * Returns the subset of the given locale list for which this locale list
1674 * has a matching (possibly fallback) locale. Locales appear in the same
1675 * order in the returned list as in the input list.
1676 * Options are optional parameter.
1678 %SetProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) {
1679 if (%_IsConstructCall()) {
1680 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1683 return supportedLocalesOf('dateformat', locales, %_Arguments(1));
1687 %FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf');
1688 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf);
1689 %SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf);
1693 * Returns a String value representing the result of calling ToNumber(date)
1694 * according to the effective locale and the formatting options of this
1697 function formatDate(formatter, dateValue) {
1699 if (dateValue === undefined) {
1700 dateMs = $Date.now();
1702 dateMs = $Number(dateValue);
1705 if (!$isFinite(dateMs)) {
1706 throw new $RangeError('Provided date is not in valid range.');
1709 return %InternalDateFormat(%GetImplFromInitializedIntlObject(formatter),
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),
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 (tzID === undefined) {
1741 // Special case handling (UTC, GMT).
1742 var upperID = tzID.toUpperCase();
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 = GetTimezoneNameCheckRE().exec(tzID);
1751 if (IS_NULL(match)) {
1752 throw new $RangeError('Expected Area/Location for time zone, got ' + tzID);
1755 var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]);
1757 while (match[i] !== undefined && i < match.length) {
1758 result = result + '_' + toTitleCaseWord(match[i]);
1766 * Initializes the given object so it's a valid BreakIterator instance.
1767 * Useful for subclassing.
1769 function initializeBreakIterator(iterator, locales, options) {
1770 if (%IsInitializedIntlObject(iterator)) {
1771 throw new $TypeError('Trying to re-initialize v8BreakIterator object.');
1774 if (options === undefined) {
1778 var getOption = getGetOption(options, 'breakiterator');
1780 var internalOptions = {};
1782 defineWEProperty(internalOptions, 'type', getOption(
1783 'type', 'string', ['character', 'word', 'sentence', 'line'], 'word'));
1785 var locale = resolveLocale('breakiterator', locales, options);
1786 var resolved = $Object.defineProperties({}, {
1787 requestedLocale: {value: locale.locale, writable: true},
1788 type: {value: internalOptions.type, writable: true},
1789 locale: {writable: true}
1792 var internalIterator = %CreateBreakIterator(locale.locale,
1796 %MarkAsInitializedIntlObjectOfType(iterator, 'breakiterator',
1798 $Object.defineProperty(iterator, 'resolved', {value: resolved});
1805 * Constructs Intl.v8BreakIterator object given optional locales and options
1810 %SetProperty(Intl, 'v8BreakIterator', function() {
1811 var locales = %_Arguments(0);
1812 var options = %_Arguments(1);
1814 if (!this || this === Intl) {
1815 // Constructor is called as a function.
1816 return new Intl.v8BreakIterator(locales, options);
1819 return initializeBreakIterator(toObject(this), locales, options);
1826 * BreakIterator resolvedOptions method.
1828 %SetProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions', function() {
1829 if (%_IsConstructCall()) {
1830 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1833 if (!%IsInitializedIntlObjectOfType(this, 'breakiterator')) {
1834 throw new $TypeError('resolvedOptions method called on a non-object or ' +
1835 'on a object that is not Intl.v8BreakIterator.');
1838 var segmenter = this;
1839 var locale = getOptimalLanguageTag(segmenter.resolved.requestedLocale,
1840 segmenter.resolved.locale);
1844 type: segmenter.resolved.type
1849 %FunctionSetName(Intl.v8BreakIterator.prototype.resolvedOptions,
1851 %FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions);
1852 %SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions);
1856 * Returns the subset of the given locale list for which this locale list
1857 * has a matching (possibly fallback) locale. Locales appear in the same
1858 * order in the returned list as in the input list.
1859 * Options are optional parameter.
1861 %SetProperty(Intl.v8BreakIterator, 'supportedLocalesOf', function(locales) {
1862 if (%_IsConstructCall()) {
1863 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1866 return supportedLocalesOf('breakiterator', locales, %_Arguments(1));
1870 %FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf');
1871 %FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf);
1872 %SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf);
1876 * Adopts text to segment using the iterator. Old text, if present,
1879 function adoptText(iterator, text) {
1880 %BreakIteratorAdoptText(%GetImplFromInitializedIntlObject(iterator),
1886 * Returns index of the first break in the string and moves current pointer.
1888 function first(iterator) {
1889 return %BreakIteratorFirst(%GetImplFromInitializedIntlObject(iterator));
1894 * Returns the index of the next break and moves the pointer.
1896 function next(iterator) {
1897 return %BreakIteratorNext(%GetImplFromInitializedIntlObject(iterator));
1902 * Returns index of the current break.
1904 function current(iterator) {
1905 return %BreakIteratorCurrent(%GetImplFromInitializedIntlObject(iterator));
1910 * Returns type of the current break.
1912 function breakType(iterator) {
1913 return %BreakIteratorBreakType(%GetImplFromInitializedIntlObject(iterator));
1917 addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1);
1918 addBoundMethod(Intl.v8BreakIterator, 'first', first, 0);
1919 addBoundMethod(Intl.v8BreakIterator, 'next', next, 0);
1920 addBoundMethod(Intl.v8BreakIterator, 'current', current, 0);
1921 addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0);
1923 // Save references to Intl objects and methods we use, for added security.
1924 var savedObjects = {
1925 'collator': Intl.Collator,
1926 'numberformat': Intl.NumberFormat,
1927 'dateformatall': Intl.DateTimeFormat,
1928 'dateformatdate': Intl.DateTimeFormat,
1929 'dateformattime': Intl.DateTimeFormat
1933 // Default (created with undefined locales and options parameters) collator,
1934 // number and date format instances. They'll be created as needed.
1935 var defaultObjects = {
1936 'collator': undefined,
1937 'numberformat': undefined,
1938 'dateformatall': undefined,
1939 'dateformatdate': undefined,
1940 'dateformattime': undefined,
1945 * Returns cached or newly created instance of a given service.
1946 * We cache only default instances (where no locales or options are provided).
1948 function cachedOrNewService(service, locales, options, defaults) {
1949 var useOptions = (defaults === undefined) ? options : defaults;
1950 if (locales === undefined && options === undefined) {
1951 if (defaultObjects[service] === undefined) {
1952 defaultObjects[service] = new savedObjects[service](locales, useOptions);
1954 return defaultObjects[service];
1956 return new savedObjects[service](locales, useOptions);
1961 * Compares this and that, and returns less than 0, 0 or greater than 0 value.
1962 * Overrides the built-in method.
1964 $Object.defineProperty($String.prototype, 'localeCompare', {
1965 value: function(that) {
1966 if (%_IsConstructCall()) {
1967 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1970 if (IS_NULL_OR_UNDEFINED(this)) {
1971 throw new $TypeError('Method invoked on undefined or null value.');
1974 var locales = %_Arguments(1);
1975 var options = %_Arguments(2);
1976 var collator = cachedOrNewService('collator', locales, options);
1977 return compare(collator, this, that);
1983 %FunctionSetName($String.prototype.localeCompare, 'localeCompare');
1984 %FunctionRemovePrototype($String.prototype.localeCompare);
1985 %SetNativeFlag($String.prototype.localeCompare);
1989 * Unicode normalization. This method is called with one argument that
1990 * specifies the normalization form.
1991 * If none is specified, "NFC" is assumed.
1992 * If the form is not one of "NFC", "NFD", "NFKC", or "NFKD", then throw
1993 * a RangeError Exception.
1995 $Object.defineProperty($String.prototype, 'normalize', {
1996 value: function(that) {
1997 if (%_IsConstructCall()) {
1998 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2001 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
2003 var form = $String(%_Arguments(0) || 'NFC');
2005 var normalizationForm = NORMALIZATION_FORMS.indexOf(form);
2006 if (normalizationForm === -1) {
2007 throw new $RangeError('The normalization form should be one of '
2008 + NORMALIZATION_FORMS.join(', ') + '.');
2011 return %StringNormalize(this, normalizationForm);
2017 %FunctionSetName($String.prototype.normalize, 'normalize');
2018 %FunctionRemovePrototype($String.prototype.normalize);
2019 %SetNativeFlag($String.prototype.normalize);
2023 * Formats a Number object (this) using locale and options values.
2024 * If locale or options are omitted, defaults are used.
2026 $Object.defineProperty($Number.prototype, 'toLocaleString', {
2028 if (%_IsConstructCall()) {
2029 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2032 if (!(this instanceof $Number) && typeof(this) !== 'number') {
2033 throw new $TypeError('Method invoked on an object that is not Number.');
2036 var locales = %_Arguments(0);
2037 var options = %_Arguments(1);
2038 var numberFormat = cachedOrNewService('numberformat', locales, options);
2039 return formatNumber(numberFormat, this);
2045 %FunctionSetName($Number.prototype.toLocaleString, 'toLocaleString');
2046 %FunctionRemovePrototype($Number.prototype.toLocaleString);
2047 %SetNativeFlag($Number.prototype.toLocaleString);
2051 * Returns actual formatted date or fails if date parameter is invalid.
2053 function toLocaleDateTime(date, locales, options, required, defaults, service) {
2054 if (!(date instanceof $Date)) {
2055 throw new $TypeError('Method invoked on an object that is not Date.');
2059 return 'Invalid Date';
2062 var internalOptions = toDateTimeOptions(options, required, defaults);
2065 cachedOrNewService(service, locales, options, internalOptions);
2067 return formatDate(dateFormat, date);
2072 * Formats a Date object (this) using locale and options values.
2073 * If locale or options are omitted, defaults are used - both date and time are
2074 * present in the output.
2076 $Object.defineProperty($Date.prototype, 'toLocaleString', {
2078 if (%_IsConstructCall()) {
2079 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2082 var locales = %_Arguments(0);
2083 var options = %_Arguments(1);
2084 return toLocaleDateTime(
2085 this, locales, options, 'any', 'all', 'dateformatall');
2091 %FunctionSetName($Date.prototype.toLocaleString, 'toLocaleString');
2092 %FunctionRemovePrototype($Date.prototype.toLocaleString);
2093 %SetNativeFlag($Date.prototype.toLocaleString);
2097 * Formats a Date object (this) using locale and options values.
2098 * If locale or options are omitted, defaults are used - only date is present
2101 $Object.defineProperty($Date.prototype, 'toLocaleDateString', {
2103 if (%_IsConstructCall()) {
2104 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2107 var locales = %_Arguments(0);
2108 var options = %_Arguments(1);
2109 return toLocaleDateTime(
2110 this, locales, options, 'date', 'date', 'dateformatdate');
2116 %FunctionSetName($Date.prototype.toLocaleDateString, 'toLocaleDateString');
2117 %FunctionRemovePrototype($Date.prototype.toLocaleDateString);
2118 %SetNativeFlag($Date.prototype.toLocaleDateString);
2122 * Formats a Date object (this) using locale and options values.
2123 * If locale or options are omitted, defaults are used - only time is present
2126 $Object.defineProperty($Date.prototype, 'toLocaleTimeString', {
2128 if (%_IsConstructCall()) {
2129 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2132 var locales = %_Arguments(0);
2133 var options = %_Arguments(1);
2134 return toLocaleDateTime(
2135 this, locales, options, 'time', 'time', 'dateformattime');
2141 %FunctionSetName($Date.prototype.toLocaleTimeString, 'toLocaleTimeString');
2142 %FunctionRemovePrototype($Date.prototype.toLocaleTimeString);
2143 %SetNativeFlag($Date.prototype.toLocaleTimeString);