1 // Copyright 2013 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // ECMAScript 402 API implementation.
8 * Intl object is a single object that has some named properties,
9 * all of which are constructors.
11 (function(global, utils) {
15 %CheckIsBootstrapping();
17 // -------------------------------------------------------------------
25 var GlobalBoolean = global.Boolean;
26 var GlobalDate = global.Date;
27 var GlobalNumber = global.Number;
28 var GlobalRegExp = global.RegExp;
29 var GlobalString = global.String;
31 var ObjectDefineProperties = utils.ImportNow("ObjectDefineProperties");
32 var ObjectDefineProperty = utils.ImportNow("ObjectDefineProperty");
35 var StringLastIndexOf;
42 utils.Import(function(from) {
43 ArrayIndexOf = from.ArrayIndexOf;
44 ArrayJoin = from.ArrayJoin;
45 ArrayPush = from.ArrayPush;
46 IsFinite = from.IsFinite;
48 MathFloor = from.MathFloor;
49 RegExpTest = from.RegExpTest;
50 StringIndexOf = from.StringIndexOf;
51 StringLastIndexOf = from.StringLastIndexOf;
52 StringMatch = from.StringMatch;
53 StringReplace = from.StringReplace;
54 StringSplit = from.StringSplit;
55 StringSubstr = from.StringSubstr;
56 StringSubstring = from.StringSubstring;
57 ToNumber = from.ToNumber;
60 // -------------------------------------------------------------------
64 %AddNamedProperty(global, "Intl", Intl, DONT_ENUM);
67 * Caches available locales for each service.
69 var AVAILABLE_LOCALES = {
70 'collator': UNDEFINED,
71 'numberformat': UNDEFINED,
72 'dateformat': UNDEFINED,
73 'breakiterator': UNDEFINED
77 * Caches default ICU locale.
79 var DEFAULT_ICU_LOCALE = UNDEFINED;
82 * Unicode extension regular expression.
84 var UNICODE_EXTENSION_RE = UNDEFINED;
86 function GetUnicodeExtensionRE() {
87 if (IS_UNDEFINED(UNDEFINED)) {
88 UNICODE_EXTENSION_RE = new GlobalRegExp('-u(-[a-z0-9]{2,8})+', 'g');
90 return UNICODE_EXTENSION_RE;
94 * Matches any Unicode extension.
96 var ANY_EXTENSION_RE = UNDEFINED;
98 function GetAnyExtensionRE() {
99 if (IS_UNDEFINED(ANY_EXTENSION_RE)) {
100 ANY_EXTENSION_RE = new GlobalRegExp('-[a-z0-9]{1}-.*', 'g');
102 return ANY_EXTENSION_RE;
106 * Replace quoted text (single quote, anything but the quote and quote again).
108 var QUOTED_STRING_RE = UNDEFINED;
110 function GetQuotedStringRE() {
111 if (IS_UNDEFINED(QUOTED_STRING_RE)) {
112 QUOTED_STRING_RE = new GlobalRegExp("'[^']+'", 'g');
114 return QUOTED_STRING_RE;
118 * Matches valid service name.
120 var SERVICE_RE = UNDEFINED;
122 function GetServiceRE() {
123 if (IS_UNDEFINED(SERVICE_RE)) {
125 new GlobalRegExp('^(collator|numberformat|dateformat|breakiterator)$');
131 * Validates a language tag against bcp47 spec.
132 * Actual value is assigned on first run.
134 var LANGUAGE_TAG_RE = UNDEFINED;
136 function GetLanguageTagRE() {
137 if (IS_UNDEFINED(LANGUAGE_TAG_RE)) {
138 BuildLanguageTagREs();
140 return LANGUAGE_TAG_RE;
144 * Helps find duplicate variants in the language tag.
146 var LANGUAGE_VARIANT_RE = UNDEFINED;
148 function GetLanguageVariantRE() {
149 if (IS_UNDEFINED(LANGUAGE_VARIANT_RE)) {
150 BuildLanguageTagREs();
152 return LANGUAGE_VARIANT_RE;
156 * Helps find duplicate singletons in the language tag.
158 var LANGUAGE_SINGLETON_RE = UNDEFINED;
160 function GetLanguageSingletonRE() {
161 if (IS_UNDEFINED(LANGUAGE_SINGLETON_RE)) {
162 BuildLanguageTagREs();
164 return LANGUAGE_SINGLETON_RE;
168 * Matches valid IANA time zone names.
170 var TIMEZONE_NAME_CHECK_RE = UNDEFINED;
172 function GetTimezoneNameCheckRE() {
173 if (IS_UNDEFINED(TIMEZONE_NAME_CHECK_RE)) {
174 TIMEZONE_NAME_CHECK_RE =
175 new GlobalRegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$');
177 return TIMEZONE_NAME_CHECK_RE;
181 * Adds bound method to the prototype of the given object.
183 function addBoundMethod(obj, methodName, implementation, length) {
184 %CheckIsBootstrapping();
186 if (!%IsInitializedIntlObject(this)) {
187 throw MakeTypeError(kMethodCalledOnWrongObject, methodName);
189 var internalName = '__bound' + methodName + '__';
190 if (IS_UNDEFINED(this[internalName])) {
193 if (IS_UNDEFINED(length) || length === 2) {
194 boundMethod = function(x, y) {
195 if (%_IsConstructCall()) {
196 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
198 return implementation(that, x, y);
200 } else if (length === 1) {
201 boundMethod = function(x) {
202 if (%_IsConstructCall()) {
203 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
205 return implementation(that, x);
208 boundMethod = function() {
209 if (%_IsConstructCall()) {
210 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
212 // DateTimeFormat.format needs to be 0 arg method, but can stil
213 // receive optional dateValue param. If one was provided, pass it
215 if (%_ArgumentsLength() > 0) {
216 return implementation(that, %_Arguments(0));
218 return implementation(that);
222 %FunctionSetName(boundMethod, internalName);
223 %FunctionRemovePrototype(boundMethod);
224 %SetNativeFlag(boundMethod);
225 this[internalName] = boundMethod;
227 return this[internalName];
230 %FunctionSetName(getter, methodName);
231 %FunctionRemovePrototype(getter);
232 %SetNativeFlag(getter);
234 ObjectDefineProperty(obj.prototype, methodName, {
243 * Returns an intersection of locales and service supported locales.
244 * Parameter locales is treated as a priority list.
246 function supportedLocalesOf(service, locales, options) {
247 if (IS_NULL(%_CallFunction(service, GetServiceRE(), StringMatch))) {
248 throw MakeError(kWrongServiceType, service);
251 // Provide defaults if matcher was not specified.
252 if (IS_UNDEFINED(options)) {
255 options = TO_OBJECT(options);
258 var matcher = options.localeMatcher;
259 if (!IS_UNDEFINED(matcher)) {
260 matcher = GlobalString(matcher);
261 if (matcher !== 'lookup' && matcher !== 'best fit') {
262 throw MakeRangeError(kLocaleMatcher, matcher);
265 matcher = 'best fit';
268 var requestedLocales = initializeLocaleList(locales);
270 // Cache these, they don't ever change per service.
271 if (IS_UNDEFINED(AVAILABLE_LOCALES[service])) {
272 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
275 // Use either best fit or lookup algorithm to match locales.
276 if (matcher === 'best fit') {
277 return initializeLocaleList(bestFitSupportedLocalesOf(
278 requestedLocales, AVAILABLE_LOCALES[service]));
281 return initializeLocaleList(lookupSupportedLocalesOf(
282 requestedLocales, AVAILABLE_LOCALES[service]));
287 * Returns the subset of the provided BCP 47 language priority list for which
288 * this service has a matching locale when using the BCP 47 Lookup algorithm.
289 * Locales appear in the same order in the returned list as in the input list.
291 function lookupSupportedLocalesOf(requestedLocales, availableLocales) {
292 var matchedLocales = [];
293 for (var i = 0; i < requestedLocales.length; ++i) {
294 // Remove -u- extension.
295 var locale = %_CallFunction(requestedLocales[i], GetUnicodeExtensionRE(),
298 if (!IS_UNDEFINED(availableLocales[locale])) {
299 // Push requested locale not the resolved one.
300 %_CallFunction(matchedLocales, requestedLocales[i], ArrayPush);
303 // Truncate locale if possible, if not break.
304 var pos = %_CallFunction(locale, '-', StringLastIndexOf);
308 locale = %_CallFunction(locale, 0, pos, StringSubstring);
312 return matchedLocales;
317 * Returns the subset of the provided BCP 47 language priority list for which
318 * this service has a matching locale when using the implementation
319 * dependent algorithm.
320 * Locales appear in the same order in the returned list as in the input list.
322 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) {
323 // TODO(cira): implement better best fit algorithm.
324 return lookupSupportedLocalesOf(requestedLocales, availableLocales);
329 * Returns a getOption function that extracts property value for given
330 * options object. If property is missing it returns defaultValue. If value
331 * is out of range for that property it throws RangeError.
333 function getGetOption(options, caller) {
334 if (IS_UNDEFINED(options)) throw MakeError(kDefaultOptionsMissing, caller);
336 var getOption = function getOption(property, type, values, defaultValue) {
337 if (!IS_UNDEFINED(options[property])) {
338 var value = options[property];
341 value = GlobalBoolean(value);
344 value = GlobalString(value);
347 value = GlobalNumber(value);
350 throw MakeError(kWrongValueType);
353 if (!IS_UNDEFINED(values) &&
354 %_CallFunction(values, value, ArrayIndexOf) === -1) {
355 throw MakeRangeError(kValueOutOfRange, value, caller, property);
369 * Compares a BCP 47 language priority list requestedLocales against the locales
370 * in availableLocales and determines the best available language to meet the
371 * request. Two algorithms are available to match the locales: the Lookup
372 * algorithm described in RFC 4647 section 3.4, and an implementation dependent
373 * best-fit algorithm. Independent of the locale matching algorithm, options
374 * specified through Unicode locale extension sequences are negotiated
375 * separately, taking the caller's relevant extension keys and locale data as
376 * well as client-provided options into consideration. Returns an object with
377 * a locale property whose value is the language tag of the selected locale,
378 * and properties for each key in relevantExtensionKeys providing the selected
379 * value for that key.
381 function resolveLocale(service, requestedLocales, options) {
382 requestedLocales = initializeLocaleList(requestedLocales);
384 var getOption = getGetOption(options, service);
385 var matcher = getOption('localeMatcher', 'string',
386 ['lookup', 'best fit'], 'best fit');
388 if (matcher === 'lookup') {
389 resolved = lookupMatcher(service, requestedLocales);
391 resolved = bestFitMatcher(service, requestedLocales);
399 * Returns best matched supported locale and extension info using basic
402 function lookupMatcher(service, requestedLocales) {
403 if (IS_NULL(%_CallFunction(service, GetServiceRE(), StringMatch))) {
404 throw MakeError(kWrongServiceType, service);
407 // Cache these, they don't ever change per service.
408 if (IS_UNDEFINED(AVAILABLE_LOCALES[service])) {
409 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
412 for (var i = 0; i < requestedLocales.length; ++i) {
413 // Remove all extensions.
414 var locale = %_CallFunction(requestedLocales[i], GetAnyExtensionRE(), '',
417 if (!IS_UNDEFINED(AVAILABLE_LOCALES[service][locale])) {
418 // Return the resolved locale and extension.
420 %_CallFunction(requestedLocales[i], GetUnicodeExtensionRE(),
422 var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0];
423 return {'locale': locale, 'extension': extension, 'position': i};
425 // Truncate locale if possible.
426 var pos = %_CallFunction(locale, '-', StringLastIndexOf);
430 locale = %_CallFunction(locale, 0, pos, StringSubstring);
434 // Didn't find a match, return default.
435 if (IS_UNDEFINED(DEFAULT_ICU_LOCALE)) {
436 DEFAULT_ICU_LOCALE = %GetDefaultICULocale();
439 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1};
444 * Returns best matched supported locale and extension info using
445 * implementation dependend algorithm.
447 function bestFitMatcher(service, requestedLocales) {
448 // TODO(cira): implement better best fit algorithm.
449 return lookupMatcher(service, requestedLocales);
454 * Parses Unicode extension into key - value map.
455 * Returns empty object if the extension string is invalid.
456 * We are not concerned with the validity of the values at this point.
458 function parseExtension(extension) {
459 var extensionSplit = %_CallFunction(extension, '-', StringSplit);
461 // Assume ['', 'u', ...] input, but don't throw.
462 if (extensionSplit.length <= 2 ||
463 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
467 // Key is {2}alphanum, value is {3,8}alphanum.
468 // Some keys may not have explicit values (booleans).
469 var extensionMap = {};
470 var previousKey = UNDEFINED;
471 for (var i = 2; i < extensionSplit.length; ++i) {
472 var length = extensionSplit[i].length;
473 var element = extensionSplit[i];
475 extensionMap[element] = UNDEFINED;
476 previousKey = element;
477 } else if (length >= 3 && length <=8 && !IS_UNDEFINED(previousKey)) {
478 extensionMap[previousKey] = element;
479 previousKey = UNDEFINED;
481 // There is a value that's too long, or that doesn't have a key.
491 * Populates internalOptions object with boolean key-value pairs
492 * from extensionMap and options.
493 * Returns filtered extension (number and date format constructors use
494 * Unicode extensions for passing parameters to ICU).
495 * It's used for extension-option pairs only, e.g. kn-normalization, but not
496 * for 'sensitivity' since it doesn't have extension equivalent.
497 * Extensions like nu and ca don't have options equivalent, so we place
498 * undefined in the map.property to denote that.
500 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
503 var updateExtension = function updateExtension(key, value) {
504 return '-' + key + '-' + GlobalString(value);
507 var updateProperty = function updateProperty(property, type, value) {
508 if (type === 'boolean' && (typeof value === 'string')) {
509 value = (value === 'true') ? true : false;
512 if (!IS_UNDEFINED(property)) {
513 defineWEProperty(outOptions, property, value);
517 for (var key in keyValues) {
518 if (%HasOwnProperty(keyValues, key)) {
519 var value = UNDEFINED;
520 var map = keyValues[key];
521 if (!IS_UNDEFINED(map.property)) {
522 // This may return true if user specifies numeric: 'false', since
523 // Boolean('nonempty') === true.
524 value = getOption(map.property, map.type, map.values);
526 if (!IS_UNDEFINED(value)) {
527 updateProperty(map.property, map.type, value);
528 extension += updateExtension(key, value);
531 // User options didn't have it, check Unicode extension.
532 // Here we want to convert strings 'true', 'false' into proper Boolean
533 // values (not a user error).
534 if (%HasOwnProperty(extensionMap, key)) {
535 value = extensionMap[key];
536 if (!IS_UNDEFINED(value)) {
537 updateProperty(map.property, map.type, value);
538 extension += updateExtension(key, value);
539 } else if (map.type === 'boolean') {
540 // Boolean keys are allowed not to have values in Unicode extension.
541 // Those default to true.
542 updateProperty(map.property, map.type, true);
543 extension += updateExtension(key, true);
549 return extension === ''? '' : '-u' + extension;
554 * Converts all OwnProperties into
555 * configurable: false, writable: false, enumerable: true.
557 function freezeArray(array) {
558 var l = array.length;
559 for (var i = 0; i < l; i++) {
561 ObjectDefineProperty(array, i, {value: array[i],
568 ObjectDefineProperty(array, 'length', {value: l, writable: false});
574 * It's sometimes desireable to leave user requested locale instead of ICU
575 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
576 * one, if that was what user requested).
577 * This function returns user specified tag if its maximized form matches ICU
578 * resolved locale. If not we return ICU result.
580 function getOptimalLanguageTag(original, resolved) {
581 // Returns Array<Object>, where each object has maximized and base properties.
582 // Maximized: zh -> zh-Hans-CN
583 // Base: zh-CN-u-ca-gregory -> zh-CN
584 // Take care of grandfathered or simple cases.
585 if (original === resolved) {
589 var locales = %GetLanguageTagVariants([original, resolved]);
590 if (locales[0].maximized !== locales[1].maximized) {
594 // Preserve extensions of resolved locale, but swap base tags with original.
595 var resolvedBase = new GlobalRegExp('^' + locales[1].base);
596 return %_CallFunction(resolved, resolvedBase, locales[0].base, StringReplace);
601 * Returns an Object that contains all of supported locales for a given
603 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ
604 * that is supported. This is required by the spec.
606 function getAvailableLocalesOf(service) {
607 var available = %AvailableLocalesOf(service);
609 for (var i in available) {
610 if (%HasOwnProperty(available, i)) {
611 var parts = %_CallFunction(i, /^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/,
613 if (parts !== null) {
614 // Build xx-ZZ. We don't care about the actual value,
615 // as long it's not undefined.
616 available[parts[1] + '-' + parts[3]] = null;
626 * Defines a property and sets writable and enumerable to true.
627 * Configurable is false by default.
629 function defineWEProperty(object, property, value) {
630 ObjectDefineProperty(object, property,
631 {value: value, writable: true, enumerable: true});
636 * Adds property to an object if the value is not undefined.
637 * Sets configurable descriptor to false.
639 function addWEPropertyIfDefined(object, property, value) {
640 if (!IS_UNDEFINED(value)) {
641 defineWEProperty(object, property, value);
647 * Defines a property and sets writable, enumerable and configurable to true.
649 function defineWECProperty(object, property, value) {
650 ObjectDefineProperty(object, property, {value: value,
653 configurable: true});
658 * Adds property to an object if the value is not undefined.
659 * Sets all descriptors to true.
661 function addWECPropertyIfDefined(object, property, value) {
662 if (!IS_UNDEFINED(value)) {
663 defineWECProperty(object, property, value);
669 * Returns titlecased word, aMeRricA -> America.
671 function toTitleCaseWord(word) {
672 return %StringToUpperCase(%_CallFunction(word, 0, 1, StringSubstr)) +
673 %StringToLowerCase(%_CallFunction(word, 1, StringSubstr));
677 * Canonicalizes the language tag, or throws in case the tag is invalid.
679 function canonicalizeLanguageTag(localeID) {
680 // null is typeof 'object' so we have to do extra check.
681 if (typeof localeID !== 'string' && typeof localeID !== 'object' ||
683 throw MakeTypeError(kLanguageID);
686 var localeString = GlobalString(localeID);
688 if (isValidLanguageTag(localeString) === false) {
689 throw MakeRangeError(kInvalidLanguageTag, localeString);
692 // This call will strip -kn but not -kn-true extensions.
693 // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265.
694 // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after
695 // upgrade to ICU 4.9.
696 var tag = %CanonicalizeLanguageTag(localeString);
697 if (tag === 'invalid-tag') {
698 throw MakeRangeError(kInvalidLanguageTag, localeString);
706 * Returns an array where all locales are canonicalized and duplicates removed.
707 * Throws on locales that are not well formed BCP47 tags.
709 function initializeLocaleList(locales) {
711 if (IS_UNDEFINED(locales)) {
712 // Constructor is called without arguments.
715 // We allow single string localeID.
716 if (typeof locales === 'string') {
717 %_CallFunction(seen, canonicalizeLanguageTag(locales), ArrayPush);
718 return freezeArray(seen);
721 var o = TO_OBJECT(locales);
722 var len = TO_UINT32(o.length);
724 for (var k = 0; k < len; k++) {
728 var tag = canonicalizeLanguageTag(value);
730 if (%_CallFunction(seen, tag, ArrayIndexOf) === -1) {
731 %_CallFunction(seen, tag, ArrayPush);
737 return freezeArray(seen);
742 * Validates the language tag. Section 2.2.9 of the bcp47 spec
743 * defines a valid tag.
745 * ICU is too permissible and lets invalid tags, like
746 * hant-cmn-cn, through.
748 * Returns false if the language tag is invalid.
750 function isValidLanguageTag(locale) {
751 // Check if it's well-formed, including grandfadered tags.
752 if (!%_CallFunction(GetLanguageTagRE(), locale, RegExpTest)) {
756 // Just return if it's a x- form. It's all private.
757 if (%_CallFunction(locale, 'x-', StringIndexOf) === 0) {
761 // Check if there are any duplicate variants or singletons (extensions).
763 // Remove private use section.
764 locale = %_CallFunction(locale, /-x-/, StringSplit)[0];
766 // Skip language since it can match variant regex, so we start from 1.
767 // We are matching i-klingon here, but that's ok, since i-klingon-klingon
768 // is not valid and would fail LANGUAGE_TAG_RE test.
771 var parts = %_CallFunction(locale, /-/, StringSplit);
772 for (var i = 1; i < parts.length; i++) {
773 var value = parts[i];
774 if (%_CallFunction(GetLanguageVariantRE(), value, RegExpTest) &&
775 extensions.length === 0) {
776 if (%_CallFunction(variants, value, ArrayIndexOf) === -1) {
777 %_CallFunction(variants, value, ArrayPush);
783 if (%_CallFunction(GetLanguageSingletonRE(), value, RegExpTest)) {
784 if (%_CallFunction(extensions, value, ArrayIndexOf) === -1) {
785 %_CallFunction(extensions, value, ArrayPush);
797 * Builds a regular expresion that validates the language tag
798 * against bcp47 spec.
799 * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF.
800 * Runs on load and initializes the global REs.
802 function BuildLanguageTagREs() {
803 var alpha = '[a-zA-Z]';
805 var alphanum = '(' + alpha + '|' + digit + ')';
806 var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' +
807 'zh-min|zh-min-nan|zh-xiang)';
808 var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' +
809 'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' +
810 'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)';
811 var grandfathered = '(' + irregular + '|' + regular + ')';
812 var privateUse = '(x(-' + alphanum + '{1,8})+)';
814 var singleton = '(' + digit + '|[A-WY-Za-wy-z])';
815 LANGUAGE_SINGLETON_RE = new GlobalRegExp('^' + singleton + '$', 'i');
817 var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)';
819 var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))';
820 LANGUAGE_VARIANT_RE = new GlobalRegExp('^' + variant + '$', 'i');
822 var region = '(' + alpha + '{2}|' + digit + '{3})';
823 var script = '(' + alpha + '{4})';
824 var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})';
825 var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' +
827 var langTag = language + '(-' + script + ')?(-' + region + ')?(-' +
828 variant + ')*(-' + extension + ')*(-' + privateUse + ')?';
831 '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$';
832 LANGUAGE_TAG_RE = new GlobalRegExp(languageTag, 'i');
836 * Initializes the given object so it's a valid Collator instance.
837 * Useful for subclassing.
839 function initializeCollator(collator, locales, options) {
840 if (%IsInitializedIntlObject(collator)) {
841 throw MakeTypeError(kReinitializeIntl, "Collator");
844 if (IS_UNDEFINED(options)) {
848 var getOption = getGetOption(options, 'collator');
850 var internalOptions = {};
852 defineWEProperty(internalOptions, 'usage', getOption(
853 'usage', 'string', ['sort', 'search'], 'sort'));
855 var sensitivity = getOption('sensitivity', 'string',
856 ['base', 'accent', 'case', 'variant']);
857 if (IS_UNDEFINED(sensitivity) && internalOptions.usage === 'sort') {
858 sensitivity = 'variant';
860 defineWEProperty(internalOptions, 'sensitivity', sensitivity);
862 defineWEProperty(internalOptions, 'ignorePunctuation', getOption(
863 'ignorePunctuation', 'boolean', UNDEFINED, false));
865 var locale = resolveLocale('collator', locales, options);
867 // ICU can't take kb, kc... parameters through localeID, so we need to pass
869 // One exception is -co- which has to be part of the extension, but only for
870 // usage: sort, and its value can't be 'standard' or 'search'.
871 var extensionMap = parseExtension(locale.extension);
874 * Map of Unicode extensions to option properties, and their values and types,
877 var COLLATOR_KEY_MAP = {
878 'kn': {'property': 'numeric', 'type': 'boolean'},
879 'kf': {'property': 'caseFirst', 'type': 'string',
880 'values': ['false', 'lower', 'upper']}
884 options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions);
886 var collation = 'default';
888 if (%HasOwnProperty(extensionMap, 'co') && internalOptions.usage === 'sort') {
891 * Allowed -u-co- values. List taken from:
892 * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml
894 var ALLOWED_CO_VALUES = [
895 'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic',
896 'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin'
899 if (%_CallFunction(ALLOWED_CO_VALUES, extensionMap.co, ArrayIndexOf) !==
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 %AddNamedProperty(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(TO_OBJECT(this), locales, options);
962 * Collator resolvedOptions method.
964 %AddNamedProperty(Intl.Collator.prototype, 'resolvedOptions', function() {
965 if (%_IsConstructCall()) {
966 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
969 if (!%IsInitializedIntlObjectOfType(this, 'collator')) {
970 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "Collator");
974 var locale = getOptimalLanguageTag(coll.resolved.requestedLocale,
975 coll.resolved.locale);
979 usage: coll.resolved.usage,
980 sensitivity: coll.resolved.sensitivity,
981 ignorePunctuation: coll.resolved.ignorePunctuation,
982 numeric: coll.resolved.numeric,
983 caseFirst: coll.resolved.caseFirst,
984 collation: coll.resolved.collation
989 %FunctionSetName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions');
990 %FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions);
991 %SetNativeFlag(Intl.Collator.prototype.resolvedOptions);
995 * Returns the subset of the given locale list for which this locale list
996 * has a matching (possibly fallback) locale. Locales appear in the same
997 * order in the returned list as in the input list.
998 * Options are optional parameter.
1000 %AddNamedProperty(Intl.Collator, 'supportedLocalesOf', function(locales) {
1001 if (%_IsConstructCall()) {
1002 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1005 return supportedLocalesOf('collator', locales, %_Arguments(1));
1009 %FunctionSetName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf');
1010 %FunctionRemovePrototype(Intl.Collator.supportedLocalesOf);
1011 %SetNativeFlag(Intl.Collator.supportedLocalesOf);
1015 * When the compare method is called with two arguments x and y, it returns a
1016 * Number other than NaN that represents the result of a locale-sensitive
1017 * String comparison of x with y.
1018 * The result is intended to order String values in the sort order specified
1019 * by the effective locale and collation options computed during construction
1020 * of this Collator object, and will be negative, zero, or positive, depending
1021 * on whether x comes before y in the sort order, the Strings are equal under
1022 * the sort order, or x comes after y in the sort order, respectively.
1024 function compare(collator, x, y) {
1025 return %InternalCompare(%GetImplFromInitializedIntlObject(collator),
1026 GlobalString(x), GlobalString(y));
1030 addBoundMethod(Intl.Collator, 'compare', compare, 2);
1033 * Verifies that the input is a well-formed ISO 4217 currency code.
1034 * Don't uppercase to test. It could convert invalid code into a valid one.
1035 * For example \u00DFP (Eszett+P) becomes SSP.
1037 function isWellFormedCurrencyCode(currency) {
1038 return typeof currency == "string" &&
1039 currency.length == 3 &&
1040 %_CallFunction(currency, /[^A-Za-z]/, StringMatch) == null;
1045 * Returns the valid digit count for a property, or throws RangeError on
1046 * a value out of the range.
1048 function getNumberOption(options, property, min, max, fallback) {
1049 var value = options[property];
1050 if (!IS_UNDEFINED(value)) {
1051 value = GlobalNumber(value);
1052 if (IsNaN(value) || value < min || value > max) {
1053 throw MakeRangeError(kPropertyValueOutOfRange, property);
1055 return MathFloor(value);
1063 * Initializes the given object so it's a valid NumberFormat instance.
1064 * Useful for subclassing.
1066 function initializeNumberFormat(numberFormat, locales, options) {
1067 if (%IsInitializedIntlObject(numberFormat)) {
1068 throw MakeTypeError(kReinitializeIntl, "NumberFormat");
1071 if (IS_UNDEFINED(options)) {
1075 var getOption = getGetOption(options, 'numberformat');
1077 var locale = resolveLocale('numberformat', locales, options);
1079 var internalOptions = {};
1080 defineWEProperty(internalOptions, 'style', getOption(
1081 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal'));
1083 var currency = getOption('currency', 'string');
1084 if (!IS_UNDEFINED(currency) && !isWellFormedCurrencyCode(currency)) {
1085 throw MakeRangeError(kInvalidCurrencyCode, currency);
1088 if (internalOptions.style === 'currency' && IS_UNDEFINED(currency)) {
1089 throw MakeTypeError(kCurrencyCode);
1092 var currencyDisplay = getOption(
1093 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
1094 if (internalOptions.style === 'currency') {
1095 defineWEProperty(internalOptions, 'currency', %StringToUpperCase(currency));
1096 defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay);
1100 var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
1101 defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid);
1103 var mnfd = options['minimumFractionDigits'];
1104 var mxfd = options['maximumFractionDigits'];
1105 if (!IS_UNDEFINED(mnfd) || !internalOptions.style === 'currency') {
1106 mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0);
1107 defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd);
1110 if (!IS_UNDEFINED(mxfd) || !internalOptions.style === 'currency') {
1111 mnfd = IS_UNDEFINED(mnfd) ? 0 : mnfd;
1112 fallback_limit = (mnfd > 3) ? mnfd : 3;
1113 mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, fallback_limit);
1114 defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd);
1117 var mnsd = options['minimumSignificantDigits'];
1118 var mxsd = options['maximumSignificantDigits'];
1119 if (!IS_UNDEFINED(mnsd) || !IS_UNDEFINED(mxsd)) {
1120 mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0);
1121 defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd);
1123 mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21);
1124 defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd);
1128 defineWEProperty(internalOptions, 'useGrouping', getOption(
1129 'useGrouping', 'boolean', UNDEFINED, true));
1131 // ICU prefers options to be passed using -u- extension key/values for
1132 // number format, so we need to build that.
1133 var extensionMap = parseExtension(locale.extension);
1136 * Map of Unicode extensions to option properties, and their values and types,
1137 * for a number format.
1139 var NUMBER_FORMAT_KEY_MAP = {
1140 'nu': {'property': UNDEFINED, 'type': 'string'}
1143 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP,
1144 getOption, internalOptions);
1146 var requestedLocale = locale.locale + extension;
1147 var resolved = ObjectDefineProperties({}, {
1148 currency: {writable: true},
1149 currencyDisplay: {writable: true},
1150 locale: {writable: true},
1151 maximumFractionDigits: {writable: true},
1152 minimumFractionDigits: {writable: true},
1153 minimumIntegerDigits: {writable: true},
1154 numberingSystem: {writable: true},
1155 requestedLocale: {value: requestedLocale, writable: true},
1156 style: {value: internalOptions.style, writable: true},
1157 useGrouping: {writable: true}
1159 if (%HasOwnProperty(internalOptions, 'minimumSignificantDigits')) {
1160 defineWEProperty(resolved, 'minimumSignificantDigits', UNDEFINED);
1162 if (%HasOwnProperty(internalOptions, 'maximumSignificantDigits')) {
1163 defineWEProperty(resolved, 'maximumSignificantDigits', UNDEFINED);
1165 var formatter = %CreateNumberFormat(requestedLocale,
1169 if (internalOptions.style === 'currency') {
1170 ObjectDefineProperty(resolved, 'currencyDisplay', {value: currencyDisplay,
1174 %MarkAsInitializedIntlObjectOfType(numberFormat, 'numberformat', formatter);
1175 ObjectDefineProperty(numberFormat, 'resolved', {value: resolved});
1177 return numberFormat;
1182 * Constructs Intl.NumberFormat object given optional locales and options
1187 %AddNamedProperty(Intl, 'NumberFormat', function() {
1188 var locales = %_Arguments(0);
1189 var options = %_Arguments(1);
1191 if (!this || this === Intl) {
1192 // Constructor is called as a function.
1193 return new Intl.NumberFormat(locales, options);
1196 return initializeNumberFormat(TO_OBJECT(this), locales, options);
1203 * NumberFormat resolvedOptions method.
1205 %AddNamedProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() {
1206 if (%_IsConstructCall()) {
1207 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1210 if (!%IsInitializedIntlObjectOfType(this, 'numberformat')) {
1211 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "NumberFormat");
1215 var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1216 format.resolved.locale);
1220 numberingSystem: format.resolved.numberingSystem,
1221 style: format.resolved.style,
1222 useGrouping: format.resolved.useGrouping,
1223 minimumIntegerDigits: format.resolved.minimumIntegerDigits,
1224 minimumFractionDigits: format.resolved.minimumFractionDigits,
1225 maximumFractionDigits: format.resolved.maximumFractionDigits,
1228 if (result.style === 'currency') {
1229 defineWECProperty(result, 'currency', format.resolved.currency);
1230 defineWECProperty(result, 'currencyDisplay',
1231 format.resolved.currencyDisplay);
1234 if (%HasOwnProperty(format.resolved, 'minimumSignificantDigits')) {
1235 defineWECProperty(result, 'minimumSignificantDigits',
1236 format.resolved.minimumSignificantDigits);
1239 if (%HasOwnProperty(format.resolved, 'maximumSignificantDigits')) {
1240 defineWECProperty(result, 'maximumSignificantDigits',
1241 format.resolved.maximumSignificantDigits);
1248 %FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions,
1250 %FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions);
1251 %SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions);
1255 * Returns the subset of the given locale list for which this locale list
1256 * has a matching (possibly fallback) locale. Locales appear in the same
1257 * order in the returned list as in the input list.
1258 * Options are optional parameter.
1260 %AddNamedProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) {
1261 if (%_IsConstructCall()) {
1262 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1265 return supportedLocalesOf('numberformat', locales, %_Arguments(1));
1269 %FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf');
1270 %FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf);
1271 %SetNativeFlag(Intl.NumberFormat.supportedLocalesOf);
1275 * Returns a String value representing the result of calling ToNumber(value)
1276 * according to the effective locale and the formatting options of this
1279 function formatNumber(formatter, value) {
1280 // Spec treats -0 and +0 as 0.
1281 var number = ToNumber(value) + 0;
1283 return %InternalNumberFormat(%GetImplFromInitializedIntlObject(formatter),
1289 * Returns a Number that represents string value that was passed in.
1291 function parseNumber(formatter, value) {
1292 return %InternalNumberParse(%GetImplFromInitializedIntlObject(formatter),
1293 GlobalString(value));
1297 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1);
1298 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1);
1301 * Returns a string that matches LDML representation of the options object.
1303 function toLDMLString(options) {
1304 var getOption = getGetOption(options, 'dateformat');
1306 var ldmlString = '';
1308 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']);
1309 ldmlString += appendToLDMLString(
1310 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'});
1312 option = getOption('era', 'string', ['narrow', 'short', 'long']);
1313 ldmlString += appendToLDMLString(
1314 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'});
1316 option = getOption('year', 'string', ['2-digit', 'numeric']);
1317 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'});
1319 option = getOption('month', 'string',
1320 ['2-digit', 'numeric', 'narrow', 'short', 'long']);
1321 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M',
1322 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'});
1324 option = getOption('day', 'string', ['2-digit', 'numeric']);
1325 ldmlString += appendToLDMLString(
1326 option, {'2-digit': 'dd', 'numeric': 'd'});
1328 var hr12 = getOption('hour12', 'boolean');
1329 option = getOption('hour', 'string', ['2-digit', 'numeric']);
1330 if (IS_UNDEFINED(hr12)) {
1331 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'});
1332 } else if (hr12 === true) {
1333 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'});
1335 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'});
1338 option = getOption('minute', 'string', ['2-digit', 'numeric']);
1339 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'});
1341 option = getOption('second', 'string', ['2-digit', 'numeric']);
1342 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'});
1344 option = getOption('timeZoneName', 'string', ['short', 'long']);
1345 ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'});
1352 * Returns either LDML equivalent of the current option or empty string.
1354 function appendToLDMLString(option, pairs) {
1355 if (!IS_UNDEFINED(option)) {
1356 return pairs[option];
1364 * Returns object that matches LDML representation of the date.
1366 function fromLDMLString(ldmlString) {
1367 // First remove '' quoted text, so we lose 'Uhr' strings.
1368 ldmlString = %_CallFunction(ldmlString, GetQuotedStringRE(), '',
1372 var match = %_CallFunction(ldmlString, /E{3,5}/g, StringMatch);
1373 options = appendToDateTimeObject(
1374 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'});
1376 match = %_CallFunction(ldmlString, /G{3,5}/g, StringMatch);
1377 options = appendToDateTimeObject(
1378 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'});
1380 match = %_CallFunction(ldmlString, /y{1,2}/g, StringMatch);
1381 options = appendToDateTimeObject(
1382 options, 'year', match, {y: 'numeric', yy: '2-digit'});
1384 match = %_CallFunction(ldmlString, /M{1,5}/g, StringMatch);
1385 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit',
1386 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'});
1388 // Sometimes we get L instead of M for month - standalone name.
1389 match = %_CallFunction(ldmlString, /L{1,5}/g, StringMatch);
1390 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit',
1391 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'});
1393 match = %_CallFunction(ldmlString, /d{1,2}/g, StringMatch);
1394 options = appendToDateTimeObject(
1395 options, 'day', match, {d: 'numeric', dd: '2-digit'});
1397 match = %_CallFunction(ldmlString, /h{1,2}/g, StringMatch);
1398 if (match !== null) {
1399 options['hour12'] = true;
1401 options = appendToDateTimeObject(
1402 options, 'hour', match, {h: 'numeric', hh: '2-digit'});
1404 match = %_CallFunction(ldmlString, /H{1,2}/g, StringMatch);
1405 if (match !== null) {
1406 options['hour12'] = false;
1408 options = appendToDateTimeObject(
1409 options, 'hour', match, {H: 'numeric', HH: '2-digit'});
1411 match = %_CallFunction(ldmlString, /m{1,2}/g, StringMatch);
1412 options = appendToDateTimeObject(
1413 options, 'minute', match, {m: 'numeric', mm: '2-digit'});
1415 match = %_CallFunction(ldmlString, /s{1,2}/g, StringMatch);
1416 options = appendToDateTimeObject(
1417 options, 'second', match, {s: 'numeric', ss: '2-digit'});
1419 match = %_CallFunction(ldmlString, /z|zzzz/g, StringMatch);
1420 options = appendToDateTimeObject(
1421 options, 'timeZoneName', match, {z: 'short', zzzz: 'long'});
1427 function appendToDateTimeObject(options, option, match, pairs) {
1428 if (IS_NULL(match)) {
1429 if (!%HasOwnProperty(options, option)) {
1430 defineWEProperty(options, option, UNDEFINED);
1435 var property = match[0];
1436 defineWEProperty(options, option, pairs[property]);
1443 * Returns options with at least default values in it.
1445 function toDateTimeOptions(options, required, defaults) {
1446 if (IS_UNDEFINED(options)) {
1449 options = TO_OBJECT(options);
1452 var needsDefault = true;
1453 if ((required === 'date' || required === 'any') &&
1454 (!IS_UNDEFINED(options.weekday) || !IS_UNDEFINED(options.year) ||
1455 !IS_UNDEFINED(options.month) || !IS_UNDEFINED(options.day))) {
1456 needsDefault = false;
1459 if ((required === 'time' || required === 'any') &&
1460 (!IS_UNDEFINED(options.hour) || !IS_UNDEFINED(options.minute) ||
1461 !IS_UNDEFINED(options.second))) {
1462 needsDefault = false;
1465 if (needsDefault && (defaults === 'date' || defaults === 'all')) {
1466 ObjectDefineProperty(options, 'year', {value: 'numeric',
1469 configurable: true});
1470 ObjectDefineProperty(options, 'month', {value: 'numeric',
1473 configurable: true});
1474 ObjectDefineProperty(options, 'day', {value: 'numeric',
1477 configurable: true});
1480 if (needsDefault && (defaults === 'time' || defaults === 'all')) {
1481 ObjectDefineProperty(options, 'hour', {value: 'numeric',
1484 configurable: true});
1485 ObjectDefineProperty(options, 'minute', {value: 'numeric',
1488 configurable: true});
1489 ObjectDefineProperty(options, 'second', {value: 'numeric',
1492 configurable: true});
1500 * Initializes the given object so it's a valid DateTimeFormat instance.
1501 * Useful for subclassing.
1503 function initializeDateTimeFormat(dateFormat, locales, options) {
1505 if (%IsInitializedIntlObject(dateFormat)) {
1506 throw MakeTypeError(kReinitializeIntl, "DateTimeFormat");
1509 if (IS_UNDEFINED(options)) {
1513 var locale = resolveLocale('dateformat', locales, options);
1515 options = toDateTimeOptions(options, 'any', 'date');
1517 var getOption = getGetOption(options, 'dateformat');
1519 // We implement only best fit algorithm, but still need to check
1520 // if the formatMatcher values are in range.
1521 var matcher = getOption('formatMatcher', 'string',
1522 ['basic', 'best fit'], 'best fit');
1524 // Build LDML string for the skeleton that we pass to the formatter.
1525 var ldmlString = toLDMLString(options);
1527 // Filter out supported extension keys so we know what to put in resolved
1528 // section later on.
1529 // We need to pass calendar and number system to the method.
1530 var tz = canonicalizeTimeZoneID(options.timeZone);
1532 // ICU prefers options to be passed using -u- extension key/values, so
1533 // we need to build that.
1534 var internalOptions = {};
1535 var extensionMap = parseExtension(locale.extension);
1538 * Map of Unicode extensions to option properties, and their values and types,
1539 * for a date/time format.
1541 var DATETIME_FORMAT_KEY_MAP = {
1542 'ca': {'property': UNDEFINED, 'type': 'string'},
1543 'nu': {'property': UNDEFINED, 'type': 'string'}
1546 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP,
1547 getOption, internalOptions);
1549 var requestedLocale = locale.locale + extension;
1550 var resolved = ObjectDefineProperties({}, {
1551 calendar: {writable: true},
1552 day: {writable: true},
1553 era: {writable: true},
1554 hour12: {writable: true},
1555 hour: {writable: true},
1556 locale: {writable: true},
1557 minute: {writable: true},
1558 month: {writable: true},
1559 numberingSystem: {writable: true},
1560 pattern: {writable: true},
1561 requestedLocale: {value: requestedLocale, writable: true},
1562 second: {writable: true},
1563 timeZone: {writable: true},
1564 timeZoneName: {writable: true},
1565 tz: {value: tz, writable: true},
1566 weekday: {writable: true},
1567 year: {writable: true}
1570 var formatter = %CreateDateTimeFormat(
1571 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved);
1573 if (!IS_UNDEFINED(tz) && tz !== resolved.timeZone) {
1574 throw MakeRangeError(kUnsupportedTimeZone, tz);
1577 %MarkAsInitializedIntlObjectOfType(dateFormat, 'dateformat', formatter);
1578 ObjectDefineProperty(dateFormat, 'resolved', {value: resolved});
1585 * Constructs Intl.DateTimeFormat object given optional locales and options
1590 %AddNamedProperty(Intl, 'DateTimeFormat', function() {
1591 var locales = %_Arguments(0);
1592 var options = %_Arguments(1);
1594 if (!this || this === Intl) {
1595 // Constructor is called as a function.
1596 return new Intl.DateTimeFormat(locales, options);
1599 return initializeDateTimeFormat(TO_OBJECT(this), locales, options);
1606 * DateTimeFormat resolvedOptions method.
1608 %AddNamedProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
1609 if (%_IsConstructCall()) {
1610 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1613 if (!%IsInitializedIntlObjectOfType(this, 'dateformat')) {
1614 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "DateTimeFormat");
1618 * Maps ICU calendar names into LDML type.
1620 var ICU_CALENDAR_MAP = {
1621 'gregorian': 'gregory',
1622 'japanese': 'japanese',
1623 'buddhist': 'buddhist',
1625 'persian': 'persian',
1626 'islamic-civil': 'islamicc',
1627 'islamic': 'islamic',
1629 'chinese': 'chinese',
1632 'ethiopic': 'ethiopic',
1633 'ethiopic-amete-alem': 'ethioaa'
1637 var fromPattern = fromLDMLString(format.resolved.pattern);
1638 var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar];
1639 if (IS_UNDEFINED(userCalendar)) {
1640 // Use ICU name if we don't have a match. It shouldn't happen, but
1641 // it would be too strict to throw for this.
1642 userCalendar = format.resolved.calendar;
1645 var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1646 format.resolved.locale);
1650 numberingSystem: format.resolved.numberingSystem,
1651 calendar: userCalendar,
1652 timeZone: format.resolved.timeZone
1655 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName);
1656 addWECPropertyIfDefined(result, 'era', fromPattern.era);
1657 addWECPropertyIfDefined(result, 'year', fromPattern.year);
1658 addWECPropertyIfDefined(result, 'month', fromPattern.month);
1659 addWECPropertyIfDefined(result, 'day', fromPattern.day);
1660 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday);
1661 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12);
1662 addWECPropertyIfDefined(result, 'hour', fromPattern.hour);
1663 addWECPropertyIfDefined(result, 'minute', fromPattern.minute);
1664 addWECPropertyIfDefined(result, 'second', fromPattern.second);
1670 %FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions,
1672 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions);
1673 %SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions);
1677 * Returns the subset of the given locale list for which this locale list
1678 * has a matching (possibly fallback) locale. Locales appear in the same
1679 * order in the returned list as in the input list.
1680 * Options are optional parameter.
1682 %AddNamedProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) {
1683 if (%_IsConstructCall()) {
1684 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1687 return supportedLocalesOf('dateformat', locales, %_Arguments(1));
1691 %FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf');
1692 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf);
1693 %SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf);
1697 * Returns a String value representing the result of calling ToNumber(date)
1698 * according to the effective locale and the formatting options of this
1701 function formatDate(formatter, dateValue) {
1703 if (IS_UNDEFINED(dateValue)) {
1704 dateMs = %DateCurrentTime();
1706 dateMs = ToNumber(dateValue);
1709 if (!IsFinite(dateMs)) throw MakeRangeError(kDateRange);
1711 return %InternalDateFormat(%GetImplFromInitializedIntlObject(formatter),
1712 new GlobalDate(dateMs));
1717 * Returns a Date object representing the result of calling ToString(value)
1718 * according to the effective locale and the formatting options of this
1720 * Returns undefined if date string cannot be parsed.
1722 function parseDate(formatter, value) {
1723 return %InternalDateParse(%GetImplFromInitializedIntlObject(formatter),
1724 GlobalString(value));
1728 // 0 because date is optional argument.
1729 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0);
1730 addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1);
1734 * Returns canonical Area/Location name, or throws an exception if the zone
1735 * name is invalid IANA name.
1737 function canonicalizeTimeZoneID(tzID) {
1738 // Skip undefined zones.
1739 if (IS_UNDEFINED(tzID)) {
1743 // Special case handling (UTC, GMT).
1744 var upperID = %StringToUpperCase(tzID);
1745 if (upperID === 'UTC' || upperID === 'GMT' ||
1746 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') {
1750 // We expect only _ and / beside ASCII letters.
1751 // All inputs should conform to Area/Location from now on.
1752 var match = %_CallFunction(tzID, GetTimezoneNameCheckRE(), StringMatch);
1753 if (IS_NULL(match)) throw MakeRangeError(kExpectedLocation, tzID);
1755 var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]);
1757 while (!IS_UNDEFINED(match[i]) && 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 MakeTypeError(kReinitializeIntl, "v8BreakIterator");
1774 if (IS_UNDEFINED(options)) {
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 = ObjectDefineProperties({}, {
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 ObjectDefineProperty(iterator, 'resolved', {value: resolved});
1805 * Constructs Intl.v8BreakIterator object given optional locales and options
1810 %AddNamedProperty(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(TO_OBJECT(this), locales, options);
1826 * BreakIterator resolvedOptions method.
1828 %AddNamedProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions',
1830 if (%_IsConstructCall()) {
1831 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1834 if (!%IsInitializedIntlObjectOfType(this, 'breakiterator')) {
1835 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "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 %AddNamedProperty(Intl.v8BreakIterator, 'supportedLocalesOf',
1863 if (%_IsConstructCall()) {
1864 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1867 return supportedLocalesOf('breakiterator', locales, %_Arguments(1));
1871 %FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf');
1872 %FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf);
1873 %SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf);
1877 * Adopts text to segment using the iterator. Old text, if present,
1880 function adoptText(iterator, text) {
1881 %BreakIteratorAdoptText(%GetImplFromInitializedIntlObject(iterator),
1882 GlobalString(text));
1887 * Returns index of the first break in the string and moves current pointer.
1889 function first(iterator) {
1890 return %BreakIteratorFirst(%GetImplFromInitializedIntlObject(iterator));
1895 * Returns the index of the next break and moves the pointer.
1897 function next(iterator) {
1898 return %BreakIteratorNext(%GetImplFromInitializedIntlObject(iterator));
1903 * Returns index of the current break.
1905 function current(iterator) {
1906 return %BreakIteratorCurrent(%GetImplFromInitializedIntlObject(iterator));
1911 * Returns type of the current break.
1913 function breakType(iterator) {
1914 return %BreakIteratorBreakType(%GetImplFromInitializedIntlObject(iterator));
1918 addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1);
1919 addBoundMethod(Intl.v8BreakIterator, 'first', first, 0);
1920 addBoundMethod(Intl.v8BreakIterator, 'next', next, 0);
1921 addBoundMethod(Intl.v8BreakIterator, 'current', current, 0);
1922 addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0);
1924 // Save references to Intl objects and methods we use, for added security.
1925 var savedObjects = {
1926 'collator': Intl.Collator,
1927 'numberformat': Intl.NumberFormat,
1928 'dateformatall': Intl.DateTimeFormat,
1929 'dateformatdate': Intl.DateTimeFormat,
1930 'dateformattime': Intl.DateTimeFormat
1934 // Default (created with undefined locales and options parameters) collator,
1935 // number and date format instances. They'll be created as needed.
1936 var defaultObjects = {
1937 'collator': UNDEFINED,
1938 'numberformat': UNDEFINED,
1939 'dateformatall': UNDEFINED,
1940 'dateformatdate': UNDEFINED,
1941 'dateformattime': UNDEFINED,
1946 * Returns cached or newly created instance of a given service.
1947 * We cache only default instances (where no locales or options are provided).
1949 function cachedOrNewService(service, locales, options, defaults) {
1950 var useOptions = (IS_UNDEFINED(defaults)) ? options : defaults;
1951 if (IS_UNDEFINED(locales) && IS_UNDEFINED(options)) {
1952 if (IS_UNDEFINED(defaultObjects[service])) {
1953 defaultObjects[service] = new savedObjects[service](locales, useOptions);
1955 return defaultObjects[service];
1957 return new savedObjects[service](locales, useOptions);
1961 function OverrideFunction(object, name, f) {
1962 %CheckIsBootstrapping();
1963 ObjectDefineProperty(object, name, { value: f,
1966 enumerable: false });
1967 %FunctionSetName(f, name);
1968 %FunctionRemovePrototype(f);
1973 * Compares this and that, and returns less than 0, 0 or greater than 0 value.
1974 * Overrides the built-in method.
1976 OverrideFunction(GlobalString.prototype, 'localeCompare', function(that) {
1977 if (%_IsConstructCall()) {
1978 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1981 if (IS_NULL_OR_UNDEFINED(this)) {
1982 throw MakeTypeError(kMethodInvokedOnNullOrUndefined);
1985 var locales = %_Arguments(1);
1986 var options = %_Arguments(2);
1987 var collator = cachedOrNewService('collator', locales, options);
1988 return compare(collator, this, that);
1994 * Unicode normalization. This method is called with one argument that
1995 * specifies the normalization form.
1996 * If none is specified, "NFC" is assumed.
1997 * If the form is not one of "NFC", "NFD", "NFKC", or "NFKD", then throw
1998 * a RangeError Exception.
2001 OverrideFunction(GlobalString.prototype, 'normalize', function() {
2002 if (%_IsConstructCall()) {
2003 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2006 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
2007 var s = TO_STRING(this);
2009 var formArg = %_Arguments(0);
2010 var form = IS_UNDEFINED(formArg) ? 'NFC' : TO_STRING(formArg);
2012 var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD'];
2014 var normalizationForm =
2015 %_CallFunction(NORMALIZATION_FORMS, form, ArrayIndexOf);
2016 if (normalizationForm === -1) {
2017 throw MakeRangeError(kNormalizationForm,
2018 %_CallFunction(NORMALIZATION_FORMS, ', ', ArrayJoin));
2021 return %StringNormalize(s, normalizationForm);
2027 * Formats a Number object (this) using locale and options values.
2028 * If locale or options are omitted, defaults are used.
2030 OverrideFunction(GlobalNumber.prototype, 'toLocaleString', function() {
2031 if (%_IsConstructCall()) {
2032 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2035 if (!(this instanceof GlobalNumber) && typeof(this) !== 'number') {
2036 throw MakeTypeError(kMethodInvokedOnWrongType, "Number");
2039 var locales = %_Arguments(0);
2040 var options = %_Arguments(1);
2041 var numberFormat = cachedOrNewService('numberformat', locales, options);
2042 return formatNumber(numberFormat, this);
2048 * Returns actual formatted date or fails if date parameter is invalid.
2050 function toLocaleDateTime(date, locales, options, required, defaults, service) {
2051 if (!(date instanceof GlobalDate)) {
2052 throw MakeTypeError(kMethodInvokedOnWrongType, "Date");
2055 if (IsNaN(date)) return 'Invalid Date';
2057 var internalOptions = toDateTimeOptions(options, required, defaults);
2060 cachedOrNewService(service, locales, options, internalOptions);
2062 return formatDate(dateFormat, date);
2067 * Formats a Date object (this) using locale and options values.
2068 * If locale or options are omitted, defaults are used - both date and time are
2069 * present in the output.
2071 OverrideFunction(GlobalDate.prototype, 'toLocaleString', function() {
2072 if (%_IsConstructCall()) {
2073 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2076 var locales = %_Arguments(0);
2077 var options = %_Arguments(1);
2078 return toLocaleDateTime(
2079 this, locales, options, 'any', 'all', 'dateformatall');
2085 * Formats a Date object (this) using locale and options values.
2086 * If locale or options are omitted, defaults are used - only date is present
2089 OverrideFunction(GlobalDate.prototype, 'toLocaleDateString', function() {
2090 if (%_IsConstructCall()) {
2091 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2094 var locales = %_Arguments(0);
2095 var options = %_Arguments(1);
2096 return toLocaleDateTime(
2097 this, locales, options, 'date', 'date', 'dateformatdate');
2103 * Formats a Date object (this) using locale and options values.
2104 * If locale or options are omitted, defaults are used - only time is present
2107 OverrideFunction(GlobalDate.prototype, 'toLocaleTimeString', function() {
2108 if (%_IsConstructCall()) {
2109 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2112 var locales = %_Arguments(0);
2113 var options = %_Arguments(1);
2114 return toLocaleDateTime(
2115 this, locales, options, 'time', 'time', 'dateformattime');