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.
15 %CheckIsBootstrapping();
17 var GlobalDate = global.Date;
18 var GlobalRegExp = global.RegExp;
19 var GlobalString = global.String;
21 var undefined = global.undefined;
25 %AddNamedProperty(global, "Intl", Intl, DONT_ENUM);
27 var AVAILABLE_SERVICES = ['collator',
32 var NORMALIZATION_FORMS = ['NFC',
38 * Caches available locales for each service.
40 var AVAILABLE_LOCALES = {
41 'collator': undefined,
42 'numberformat': undefined,
43 'dateformat': undefined,
44 'breakiterator': undefined
48 * Caches default ICU locale.
50 var DEFAULT_ICU_LOCALE = undefined;
53 * Unicode extension regular expression.
55 var UNICODE_EXTENSION_RE = undefined;
57 function GetUnicodeExtensionRE() {
58 if (UNICODE_EXTENSION_RE === undefined) {
59 UNICODE_EXTENSION_RE = new GlobalRegExp('-u(-[a-z0-9]{2,8})+', 'g');
61 return UNICODE_EXTENSION_RE;
65 * Matches any Unicode extension.
67 var ANY_EXTENSION_RE = undefined;
69 function GetAnyExtensionRE() {
70 if (ANY_EXTENSION_RE === undefined) {
71 ANY_EXTENSION_RE = new GlobalRegExp('-[a-z0-9]{1}-.*', 'g');
73 return ANY_EXTENSION_RE;
77 * Replace quoted text (single quote, anything but the quote and quote again).
79 var QUOTED_STRING_RE = undefined;
81 function GetQuotedStringRE() {
82 if (QUOTED_STRING_RE === undefined) {
83 QUOTED_STRING_RE = new GlobalRegExp("'[^']+'", 'g');
85 return QUOTED_STRING_RE;
89 * Matches valid service name.
91 var SERVICE_RE = undefined;
93 function GetServiceRE() {
94 if (SERVICE_RE === undefined) {
96 new GlobalRegExp('^(collator|numberformat|dateformat|breakiterator)$');
102 * Validates a language tag against bcp47 spec.
103 * Actual value is assigned on first run.
105 var LANGUAGE_TAG_RE = undefined;
107 function GetLanguageTagRE() {
108 if (LANGUAGE_TAG_RE === undefined) {
109 BuildLanguageTagREs();
111 return LANGUAGE_TAG_RE;
115 * Helps find duplicate variants in the language tag.
117 var LANGUAGE_VARIANT_RE = undefined;
119 function GetLanguageVariantRE() {
120 if (LANGUAGE_VARIANT_RE === undefined) {
121 BuildLanguageTagREs();
123 return LANGUAGE_VARIANT_RE;
127 * Helps find duplicate singletons in the language tag.
129 var LANGUAGE_SINGLETON_RE = undefined;
131 function GetLanguageSingletonRE() {
132 if (LANGUAGE_SINGLETON_RE === undefined) {
133 BuildLanguageTagREs();
135 return LANGUAGE_SINGLETON_RE;
139 * Matches valid IANA time zone names.
141 var TIMEZONE_NAME_CHECK_RE = undefined;
143 function GetTimezoneNameCheckRE() {
144 if (TIMEZONE_NAME_CHECK_RE === undefined) {
145 TIMEZONE_NAME_CHECK_RE =
146 new GlobalRegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$');
148 return TIMEZONE_NAME_CHECK_RE;
152 * Maps ICU calendar names into LDML type.
154 var ICU_CALENDAR_MAP = {
155 'gregorian': 'gregory',
156 'japanese': 'japanese',
157 'buddhist': 'buddhist',
159 'persian': 'persian',
160 'islamic-civil': 'islamicc',
161 'islamic': 'islamic',
163 'chinese': 'chinese',
166 'ethiopic': 'ethiopic',
167 'ethiopic-amete-alem': 'ethioaa'
171 * Map of Unicode extensions to option properties, and their values and types,
174 var COLLATOR_KEY_MAP = {
175 'kn': {'property': 'numeric', 'type': 'boolean'},
176 'kf': {'property': 'caseFirst', 'type': 'string',
177 'values': ['false', 'lower', 'upper']}
181 * Map of Unicode extensions to option properties, and their values and types,
182 * for a number format.
184 var NUMBER_FORMAT_KEY_MAP = {
185 'nu': {'property': undefined, 'type': 'string'}
189 * Map of Unicode extensions to option properties, and their values and types,
190 * for a date/time format.
192 var DATETIME_FORMAT_KEY_MAP = {
193 'ca': {'property': undefined, 'type': 'string'},
194 'nu': {'property': undefined, 'type': 'string'}
198 * Allowed -u-co- values. List taken from:
199 * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml
201 var ALLOWED_CO_VALUES = [
202 'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic',
203 'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin'
207 * Error message for when function object is created with new and it's not
210 var ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR =
211 'Function object that\'s not a constructor was created with new';
215 * Adds bound method to the prototype of the given object.
217 function addBoundMethod(obj, methodName, implementation, length) {
219 if (!%IsInitializedIntlObject(this)) {
220 throw new $TypeError('Method ' + methodName + ' called on a ' +
221 'non-object or on a wrong type of object.');
223 var internalName = '__bound' + methodName + '__';
224 if (this[internalName] === undefined) {
227 if (length === undefined || length === 2) {
228 boundMethod = function(x, y) {
229 if (%_IsConstructCall()) {
230 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
232 return implementation(that, x, y);
234 } else if (length === 1) {
235 boundMethod = function(x) {
236 if (%_IsConstructCall()) {
237 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
239 return implementation(that, x);
242 boundMethod = function() {
243 if (%_IsConstructCall()) {
244 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
246 // DateTimeFormat.format needs to be 0 arg method, but can stil
247 // receive optional dateValue param. If one was provided, pass it
249 if (%_ArgumentsLength() > 0) {
250 return implementation(that, %_Arguments(0));
252 return implementation(that);
256 %FunctionSetName(boundMethod, internalName);
257 %FunctionRemovePrototype(boundMethod);
258 %SetNativeFlag(boundMethod);
259 this[internalName] = boundMethod;
261 return this[internalName];
264 %FunctionSetName(getter, methodName);
265 %FunctionRemovePrototype(getter);
266 %SetNativeFlag(getter);
268 ObjectDefineProperty(obj.prototype, methodName, {
277 * Returns an intersection of locales and service supported locales.
278 * Parameter locales is treated as a priority list.
280 function supportedLocalesOf(service, locales, options) {
281 if (IS_NULL(service.match(GetServiceRE()))) {
282 throw new $Error('Internal error, wrong service type: ' + service);
285 // Provide defaults if matcher was not specified.
286 if (options === undefined) {
289 options = ToObject(options);
292 var matcher = options.localeMatcher;
293 if (matcher !== undefined) {
294 matcher = GlobalString(matcher);
295 if (matcher !== 'lookup' && matcher !== 'best fit') {
296 throw new $RangeError('Illegal value for localeMatcher:' + matcher);
299 matcher = 'best fit';
302 var requestedLocales = initializeLocaleList(locales);
304 // Cache these, they don't ever change per service.
305 if (AVAILABLE_LOCALES[service] === undefined) {
306 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
309 // Use either best fit or lookup algorithm to match locales.
310 if (matcher === 'best fit') {
311 return initializeLocaleList(bestFitSupportedLocalesOf(
312 requestedLocales, AVAILABLE_LOCALES[service]));
315 return initializeLocaleList(lookupSupportedLocalesOf(
316 requestedLocales, AVAILABLE_LOCALES[service]));
321 * Returns the subset of the provided BCP 47 language priority list for which
322 * this service has a matching locale when using the BCP 47 Lookup algorithm.
323 * Locales appear in the same order in the returned list as in the input list.
325 function lookupSupportedLocalesOf(requestedLocales, availableLocales) {
326 var matchedLocales = [];
327 for (var i = 0; i < requestedLocales.length; ++i) {
328 // Remove -u- extension.
329 var locale = requestedLocales[i].replace(GetUnicodeExtensionRE(), '');
331 if (availableLocales[locale] !== undefined) {
332 // Push requested locale not the resolved one.
333 matchedLocales.push(requestedLocales[i]);
336 // Truncate locale if possible, if not break.
337 var pos = locale.lastIndexOf('-');
341 locale = locale.substring(0, pos);
345 return matchedLocales;
350 * Returns the subset of the provided BCP 47 language priority list for which
351 * this service has a matching locale when using the implementation
352 * dependent algorithm.
353 * Locales appear in the same order in the returned list as in the input list.
355 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) {
356 // TODO(cira): implement better best fit algorithm.
357 return lookupSupportedLocalesOf(requestedLocales, availableLocales);
362 * Returns a getOption function that extracts property value for given
363 * options object. If property is missing it returns defaultValue. If value
364 * is out of range for that property it throws RangeError.
366 function getGetOption(options, caller) {
367 if (options === undefined) {
368 throw new $Error('Internal ' + caller + ' error. ' +
369 'Default options are missing.');
372 var getOption = function getOption(property, type, values, defaultValue) {
373 if (options[property] !== undefined) {
374 var value = options[property];
377 value = $Boolean(value);
380 value = GlobalString(value);
383 value = $Number(value);
386 throw new $Error('Internal error. Wrong value type.');
388 if (values !== undefined && values.indexOf(value) === -1) {
389 throw new $RangeError('Value ' + value + ' out of range for ' + caller +
390 ' options property ' + property);
404 * Compares a BCP 47 language priority list requestedLocales against the locales
405 * in availableLocales and determines the best available language to meet the
406 * request. Two algorithms are available to match the locales: the Lookup
407 * algorithm described in RFC 4647 section 3.4, and an implementation dependent
408 * best-fit algorithm. Independent of the locale matching algorithm, options
409 * specified through Unicode locale extension sequences are negotiated
410 * separately, taking the caller's relevant extension keys and locale data as
411 * well as client-provided options into consideration. Returns an object with
412 * a locale property whose value is the language tag of the selected locale,
413 * and properties for each key in relevantExtensionKeys providing the selected
414 * value for that key.
416 function resolveLocale(service, requestedLocales, options) {
417 requestedLocales = initializeLocaleList(requestedLocales);
419 var getOption = getGetOption(options, service);
420 var matcher = getOption('localeMatcher', 'string',
421 ['lookup', 'best fit'], 'best fit');
423 if (matcher === 'lookup') {
424 resolved = lookupMatcher(service, requestedLocales);
426 resolved = bestFitMatcher(service, requestedLocales);
434 * Returns best matched supported locale and extension info using basic
437 function lookupMatcher(service, requestedLocales) {
438 if (IS_NULL(service.match(GetServiceRE()))) {
439 throw new $Error('Internal error, wrong service type: ' + service);
442 // Cache these, they don't ever change per service.
443 if (AVAILABLE_LOCALES[service] === undefined) {
444 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
447 for (var i = 0; i < requestedLocales.length; ++i) {
448 // Remove all extensions.
449 var locale = requestedLocales[i].replace(GetAnyExtensionRE(), '');
451 if (AVAILABLE_LOCALES[service][locale] !== undefined) {
452 // Return the resolved locale and extension.
453 var extensionMatch = requestedLocales[i].match(GetUnicodeExtensionRE());
454 var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0];
455 return {'locale': locale, 'extension': extension, 'position': i};
457 // Truncate locale if possible.
458 var pos = locale.lastIndexOf('-');
462 locale = locale.substring(0, pos);
466 // Didn't find a match, return default.
467 if (DEFAULT_ICU_LOCALE === undefined) {
468 DEFAULT_ICU_LOCALE = %GetDefaultICULocale();
471 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1};
476 * Returns best matched supported locale and extension info using
477 * implementation dependend algorithm.
479 function bestFitMatcher(service, requestedLocales) {
480 // TODO(cira): implement better best fit algorithm.
481 return lookupMatcher(service, requestedLocales);
486 * Parses Unicode extension into key - value map.
487 * Returns empty object if the extension string is invalid.
488 * We are not concerned with the validity of the values at this point.
490 function parseExtension(extension) {
491 var extensionSplit = extension.split('-');
493 // Assume ['', 'u', ...] input, but don't throw.
494 if (extensionSplit.length <= 2 ||
495 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
499 // Key is {2}alphanum, value is {3,8}alphanum.
500 // Some keys may not have explicit values (booleans).
501 var extensionMap = {};
502 var previousKey = undefined;
503 for (var i = 2; i < extensionSplit.length; ++i) {
504 var length = extensionSplit[i].length;
505 var element = extensionSplit[i];
507 extensionMap[element] = undefined;
508 previousKey = element;
509 } else if (length >= 3 && length <=8 && previousKey !== undefined) {
510 extensionMap[previousKey] = element;
511 previousKey = undefined;
513 // There is a value that's too long, or that doesn't have a key.
523 * Populates internalOptions object with boolean key-value pairs
524 * from extensionMap and options.
525 * Returns filtered extension (number and date format constructors use
526 * Unicode extensions for passing parameters to ICU).
527 * It's used for extension-option pairs only, e.g. kn-normalization, but not
528 * for 'sensitivity' since it doesn't have extension equivalent.
529 * Extensions like nu and ca don't have options equivalent, so we place
530 * undefined in the map.property to denote that.
532 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
535 var updateExtension = function updateExtension(key, value) {
536 return '-' + key + '-' + GlobalString(value);
539 var updateProperty = function updateProperty(property, type, value) {
540 if (type === 'boolean' && (typeof value === 'string')) {
541 value = (value === 'true') ? true : false;
544 if (property !== undefined) {
545 defineWEProperty(outOptions, property, value);
549 for (var key in keyValues) {
550 if (keyValues.hasOwnProperty(key)) {
551 var value = undefined;
552 var map = keyValues[key];
553 if (map.property !== undefined) {
554 // This may return true if user specifies numeric: 'false', since
555 // Boolean('nonempty') === true.
556 value = getOption(map.property, map.type, map.values);
558 if (value !== undefined) {
559 updateProperty(map.property, map.type, value);
560 extension += updateExtension(key, value);
563 // User options didn't have it, check Unicode extension.
564 // Here we want to convert strings 'true', 'false' into proper Boolean
565 // values (not a user error).
566 if (extensionMap.hasOwnProperty(key)) {
567 value = extensionMap[key];
568 if (value !== undefined) {
569 updateProperty(map.property, map.type, value);
570 extension += updateExtension(key, value);
571 } else if (map.type === 'boolean') {
572 // Boolean keys are allowed not to have values in Unicode extension.
573 // Those default to true.
574 updateProperty(map.property, map.type, true);
575 extension += updateExtension(key, true);
581 return extension === ''? '' : '-u' + extension;
586 * Converts all OwnProperties into
587 * configurable: false, writable: false, enumerable: true.
589 function freezeArray(array) {
590 array.forEach(function(element, index) {
591 ObjectDefineProperty(array, index, {value: element,
597 ObjectDefineProperty(array, 'length', {value: array.length,
604 * It's sometimes desireable to leave user requested locale instead of ICU
605 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
606 * one, if that was what user requested).
607 * This function returns user specified tag if its maximized form matches ICU
608 * resolved locale. If not we return ICU result.
610 function getOptimalLanguageTag(original, resolved) {
611 // Returns Array<Object>, where each object has maximized and base properties.
612 // Maximized: zh -> zh-Hans-CN
613 // Base: zh-CN-u-ca-gregory -> zh-CN
614 // Take care of grandfathered or simple cases.
615 if (original === resolved) {
619 var locales = %GetLanguageTagVariants([original, resolved]);
620 if (locales[0].maximized !== locales[1].maximized) {
624 // Preserve extensions of resolved locale, but swap base tags with original.
625 var resolvedBase = new GlobalRegExp('^' + locales[1].base);
626 return resolved.replace(resolvedBase, locales[0].base);
631 * Returns an Object that contains all of supported locales for a given
633 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ
634 * that is supported. This is required by the spec.
636 function getAvailableLocalesOf(service) {
637 var available = %AvailableLocalesOf(service);
639 for (var i in available) {
640 if (available.hasOwnProperty(i)) {
641 var parts = i.match(/^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/);
642 if (parts !== null) {
643 // Build xx-ZZ. We don't care about the actual value,
644 // as long it's not undefined.
645 available[parts[1] + '-' + parts[3]] = null;
655 * Defines a property and sets writable and enumerable to true.
656 * Configurable is false by default.
658 function defineWEProperty(object, property, value) {
659 ObjectDefineProperty(object, property,
660 {value: value, writable: true, enumerable: true});
665 * Adds property to an object if the value is not undefined.
666 * Sets configurable descriptor to false.
668 function addWEPropertyIfDefined(object, property, value) {
669 if (value !== undefined) {
670 defineWEProperty(object, property, value);
676 * Defines a property and sets writable, enumerable and configurable to true.
678 function defineWECProperty(object, property, value) {
679 ObjectDefineProperty(object, property,
683 configurable: true});
688 * Adds property to an object if the value is not undefined.
689 * Sets all descriptors to true.
691 function addWECPropertyIfDefined(object, property, value) {
692 if (value !== undefined) {
693 defineWECProperty(object, property, value);
699 * Returns titlecased word, aMeRricA -> America.
701 function toTitleCaseWord(word) {
702 return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase();
706 * Canonicalizes the language tag, or throws in case the tag is invalid.
708 function canonicalizeLanguageTag(localeID) {
709 // null is typeof 'object' so we have to do extra check.
710 if (typeof localeID !== 'string' && typeof localeID !== 'object' ||
712 throw new $TypeError('Language ID should be string or object.');
715 var localeString = GlobalString(localeID);
717 if (isValidLanguageTag(localeString) === false) {
718 throw new $RangeError('Invalid language tag: ' + localeString);
721 // This call will strip -kn but not -kn-true extensions.
722 // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265.
723 // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after
724 // upgrade to ICU 4.9.
725 var tag = %CanonicalizeLanguageTag(localeString);
726 if (tag === 'invalid-tag') {
727 throw new $RangeError('Invalid language tag: ' + localeString);
735 * Returns an array where all locales are canonicalized and duplicates removed.
736 * Throws on locales that are not well formed BCP47 tags.
738 function initializeLocaleList(locales) {
740 if (locales === undefined) {
741 // Constructor is called without arguments.
744 // We allow single string localeID.
745 if (typeof locales === 'string') {
746 seen.push(canonicalizeLanguageTag(locales));
747 return freezeArray(seen);
750 var o = ToObject(locales);
751 // Converts it to UInt32 (>>> is shr on 32bit integers).
752 var len = o.length >>> 0;
754 for (var k = 0; k < len; k++) {
758 var tag = canonicalizeLanguageTag(value);
760 if (seen.indexOf(tag) === -1) {
767 return freezeArray(seen);
772 * Validates the language tag. Section 2.2.9 of the bcp47 spec
773 * defines a valid tag.
775 * ICU is too permissible and lets invalid tags, like
776 * hant-cmn-cn, through.
778 * Returns false if the language tag is invalid.
780 function isValidLanguageTag(locale) {
781 // Check if it's well-formed, including grandfadered tags.
782 if (GetLanguageTagRE().test(locale) === false) {
786 // Just return if it's a x- form. It's all private.
787 if (locale.indexOf('x-') === 0) {
791 // Check if there are any duplicate variants or singletons (extensions).
793 // Remove private use section.
794 locale = locale.split(/-x-/)[0];
796 // Skip language since it can match variant regex, so we start from 1.
797 // We are matching i-klingon here, but that's ok, since i-klingon-klingon
798 // is not valid and would fail LANGUAGE_TAG_RE test.
801 var parts = locale.split(/-/);
802 for (var i = 1; i < parts.length; i++) {
803 var value = parts[i];
804 if (GetLanguageVariantRE().test(value) === true && extensions.length === 0) {
805 if (variants.indexOf(value) === -1) {
806 variants.push(value);
812 if (GetLanguageSingletonRE().test(value) === true) {
813 if (extensions.indexOf(value) === -1) {
814 extensions.push(value);
826 * Builds a regular expresion that validates the language tag
827 * against bcp47 spec.
828 * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF.
829 * Runs on load and initializes the global REs.
831 function BuildLanguageTagREs() {
832 var alpha = '[a-zA-Z]';
834 var alphanum = '(' + alpha + '|' + digit + ')';
835 var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' +
836 'zh-min|zh-min-nan|zh-xiang)';
837 var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' +
838 'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' +
839 'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)';
840 var grandfathered = '(' + irregular + '|' + regular + ')';
841 var privateUse = '(x(-' + alphanum + '{1,8})+)';
843 var singleton = '(' + digit + '|[A-WY-Za-wy-z])';
844 LANGUAGE_SINGLETON_RE = new GlobalRegExp('^' + singleton + '$', 'i');
846 var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)';
848 var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))';
849 LANGUAGE_VARIANT_RE = new GlobalRegExp('^' + variant + '$', 'i');
851 var region = '(' + alpha + '{2}|' + digit + '{3})';
852 var script = '(' + alpha + '{4})';
853 var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})';
854 var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' +
856 var langTag = language + '(-' + script + ')?(-' + region + ')?(-' +
857 variant + ')*(-' + extension + ')*(-' + privateUse + ')?';
860 '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$';
861 LANGUAGE_TAG_RE = new GlobalRegExp(languageTag, 'i');
865 * Initializes the given object so it's a valid Collator instance.
866 * Useful for subclassing.
868 function initializeCollator(collator, locales, options) {
869 if (%IsInitializedIntlObject(collator)) {
870 throw new $TypeError('Trying to re-initialize Collator object.');
873 if (options === undefined) {
877 var getOption = getGetOption(options, 'collator');
879 var internalOptions = {};
881 defineWEProperty(internalOptions, 'usage', getOption(
882 'usage', 'string', ['sort', 'search'], 'sort'));
884 var sensitivity = getOption('sensitivity', 'string',
885 ['base', 'accent', 'case', 'variant']);
886 if (sensitivity === undefined && internalOptions.usage === 'sort') {
887 sensitivity = 'variant';
889 defineWEProperty(internalOptions, 'sensitivity', sensitivity);
891 defineWEProperty(internalOptions, 'ignorePunctuation', getOption(
892 'ignorePunctuation', 'boolean', undefined, false));
894 var locale = resolveLocale('collator', locales, options);
896 // ICU can't take kb, kc... parameters through localeID, so we need to pass
898 // One exception is -co- which has to be part of the extension, but only for
899 // usage: sort, and its value can't be 'standard' or 'search'.
900 var extensionMap = parseExtension(locale.extension);
902 options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions);
904 var collation = 'default';
906 if (extensionMap.hasOwnProperty('co') && internalOptions.usage === 'sort') {
907 if (ALLOWED_CO_VALUES.indexOf(extensionMap.co) !== -1) {
908 extension = '-u-co-' + extensionMap.co;
909 // ICU can't tell us what the collation is, so save user's input.
910 collation = extensionMap.co;
912 } else if (internalOptions.usage === 'search') {
913 extension = '-u-co-search';
915 defineWEProperty(internalOptions, 'collation', collation);
917 var requestedLocale = locale.locale + extension;
919 // We define all properties C++ code may produce, to prevent security
920 // problems. If malicious user decides to redefine Object.prototype.locale
921 // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us").
922 // ObjectDefineProperties will either succeed defining or throw an error.
923 var resolved = ObjectDefineProperties({}, {
924 caseFirst: {writable: true},
925 collation: {value: internalOptions.collation, writable: true},
926 ignorePunctuation: {writable: true},
927 locale: {writable: true},
928 numeric: {writable: true},
929 requestedLocale: {value: requestedLocale, writable: true},
930 sensitivity: {writable: true},
931 strength: {writable: true},
932 usage: {value: internalOptions.usage, writable: true}
935 var internalCollator = %CreateCollator(requestedLocale,
939 // Writable, configurable and enumerable are set to false by default.
940 %MarkAsInitializedIntlObjectOfType(collator, 'collator', internalCollator);
941 ObjectDefineProperty(collator, 'resolved', {value: resolved});
948 * Constructs Intl.Collator object given optional locales and options
953 %AddNamedProperty(Intl, 'Collator', function() {
954 var locales = %_Arguments(0);
955 var options = %_Arguments(1);
957 if (!this || this === Intl) {
958 // Constructor is called as a function.
959 return new Intl.Collator(locales, options);
962 return initializeCollator(ToObject(this), locales, options);
969 * Collator resolvedOptions method.
971 %AddNamedProperty(Intl.Collator.prototype, 'resolvedOptions', function() {
972 if (%_IsConstructCall()) {
973 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
976 if (!%IsInitializedIntlObjectOfType(this, 'collator')) {
977 throw new $TypeError('resolvedOptions method called on a non-object ' +
978 'or on a object that is not Intl.Collator.');
982 var locale = getOptimalLanguageTag(coll.resolved.requestedLocale,
983 coll.resolved.locale);
987 usage: coll.resolved.usage,
988 sensitivity: coll.resolved.sensitivity,
989 ignorePunctuation: coll.resolved.ignorePunctuation,
990 numeric: coll.resolved.numeric,
991 caseFirst: coll.resolved.caseFirst,
992 collation: coll.resolved.collation
997 %FunctionSetName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions');
998 %FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions);
999 %SetNativeFlag(Intl.Collator.prototype.resolvedOptions);
1003 * Returns the subset of the given locale list for which this locale list
1004 * has a matching (possibly fallback) locale. Locales appear in the same
1005 * order in the returned list as in the input list.
1006 * Options are optional parameter.
1008 %AddNamedProperty(Intl.Collator, 'supportedLocalesOf', function(locales) {
1009 if (%_IsConstructCall()) {
1010 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1013 return supportedLocalesOf('collator', locales, %_Arguments(1));
1017 %FunctionSetName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf');
1018 %FunctionRemovePrototype(Intl.Collator.supportedLocalesOf);
1019 %SetNativeFlag(Intl.Collator.supportedLocalesOf);
1023 * When the compare method is called with two arguments x and y, it returns a
1024 * Number other than NaN that represents the result of a locale-sensitive
1025 * String comparison of x with y.
1026 * The result is intended to order String values in the sort order specified
1027 * by the effective locale and collation options computed during construction
1028 * of this Collator object, and will be negative, zero, or positive, depending
1029 * on whether x comes before y in the sort order, the Strings are equal under
1030 * the sort order, or x comes after y in the sort order, respectively.
1032 function compare(collator, x, y) {
1033 return %InternalCompare(%GetImplFromInitializedIntlObject(collator),
1034 GlobalString(x), GlobalString(y));
1038 addBoundMethod(Intl.Collator, 'compare', compare, 2);
1041 * Verifies that the input is a well-formed ISO 4217 currency code.
1042 * Don't uppercase to test. It could convert invalid code into a valid one.
1043 * For example \u00DFP (Eszett+P) becomes SSP.
1045 function isWellFormedCurrencyCode(currency) {
1046 return typeof currency == "string" &&
1047 currency.length == 3 &&
1048 currency.match(/[^A-Za-z]/) == null;
1053 * Returns the valid digit count for a property, or throws RangeError on
1054 * a value out of the range.
1056 function getNumberOption(options, property, min, max, fallback) {
1057 var value = options[property];
1058 if (value !== undefined) {
1059 value = $Number(value);
1060 if ($isNaN(value) || value < min || value > max) {
1061 throw new $RangeError(property + ' value is out of range.');
1063 return $floor(value);
1071 * Initializes the given object so it's a valid NumberFormat instance.
1072 * Useful for subclassing.
1074 function initializeNumberFormat(numberFormat, locales, options) {
1075 if (%IsInitializedIntlObject(numberFormat)) {
1076 throw new $TypeError('Trying to re-initialize NumberFormat object.');
1079 if (options === undefined) {
1083 var getOption = getGetOption(options, 'numberformat');
1085 var locale = resolveLocale('numberformat', locales, options);
1087 var internalOptions = {};
1088 defineWEProperty(internalOptions, 'style', getOption(
1089 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal'));
1091 var currency = getOption('currency', 'string');
1092 if (currency !== undefined && !isWellFormedCurrencyCode(currency)) {
1093 throw new $RangeError('Invalid currency code: ' + currency);
1096 if (internalOptions.style === 'currency' && currency === undefined) {
1097 throw new $TypeError('Currency code is required with currency style.');
1100 var currencyDisplay = getOption(
1101 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
1102 if (internalOptions.style === 'currency') {
1103 defineWEProperty(internalOptions, 'currency', currency.toUpperCase());
1104 defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay);
1108 var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
1109 defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid);
1111 var mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0);
1112 defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd);
1114 var mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, 3);
1115 defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd);
1117 var mnsd = options['minimumSignificantDigits'];
1118 var mxsd = options['maximumSignificantDigits'];
1119 if (mnsd !== undefined || mxsd !== undefined) {
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);
1134 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP,
1135 getOption, internalOptions);
1137 var requestedLocale = locale.locale + extension;
1138 var resolved = ObjectDefineProperties({}, {
1139 currency: {writable: true},
1140 currencyDisplay: {writable: true},
1141 locale: {writable: true},
1142 maximumFractionDigits: {writable: true},
1143 minimumFractionDigits: {writable: true},
1144 minimumIntegerDigits: {writable: true},
1145 numberingSystem: {writable: true},
1146 requestedLocale: {value: requestedLocale, writable: true},
1147 style: {value: internalOptions.style, writable: true},
1148 useGrouping: {writable: true}
1150 if (internalOptions.hasOwnProperty('minimumSignificantDigits')) {
1151 defineWEProperty(resolved, 'minimumSignificantDigits', undefined);
1153 if (internalOptions.hasOwnProperty('maximumSignificantDigits')) {
1154 defineWEProperty(resolved, 'maximumSignificantDigits', undefined);
1156 var formatter = %CreateNumberFormat(requestedLocale,
1160 // We can't get information about number or currency style from ICU, so we
1161 // assume user request was fulfilled.
1162 if (internalOptions.style === 'currency') {
1163 ObjectDefineProperty(resolved, 'currencyDisplay', {value: currencyDisplay,
1167 %MarkAsInitializedIntlObjectOfType(numberFormat, 'numberformat', formatter);
1168 ObjectDefineProperty(numberFormat, 'resolved', {value: resolved});
1170 return numberFormat;
1175 * Constructs Intl.NumberFormat object given optional locales and options
1180 %AddNamedProperty(Intl, 'NumberFormat', function() {
1181 var locales = %_Arguments(0);
1182 var options = %_Arguments(1);
1184 if (!this || this === Intl) {
1185 // Constructor is called as a function.
1186 return new Intl.NumberFormat(locales, options);
1189 return initializeNumberFormat(ToObject(this), locales, options);
1196 * NumberFormat resolvedOptions method.
1198 %AddNamedProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() {
1199 if (%_IsConstructCall()) {
1200 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1203 if (!%IsInitializedIntlObjectOfType(this, 'numberformat')) {
1204 throw new $TypeError('resolvedOptions method called on a non-object' +
1205 ' or on a object that is not Intl.NumberFormat.');
1209 var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1210 format.resolved.locale);
1214 numberingSystem: format.resolved.numberingSystem,
1215 style: format.resolved.style,
1216 useGrouping: format.resolved.useGrouping,
1217 minimumIntegerDigits: format.resolved.minimumIntegerDigits,
1218 minimumFractionDigits: format.resolved.minimumFractionDigits,
1219 maximumFractionDigits: format.resolved.maximumFractionDigits,
1222 if (result.style === 'currency') {
1223 defineWECProperty(result, 'currency', format.resolved.currency);
1224 defineWECProperty(result, 'currencyDisplay',
1225 format.resolved.currencyDisplay);
1228 if (format.resolved.hasOwnProperty('minimumSignificantDigits')) {
1229 defineWECProperty(result, 'minimumSignificantDigits',
1230 format.resolved.minimumSignificantDigits);
1233 if (format.resolved.hasOwnProperty('maximumSignificantDigits')) {
1234 defineWECProperty(result, 'maximumSignificantDigits',
1235 format.resolved.maximumSignificantDigits);
1242 %FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions,
1244 %FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions);
1245 %SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions);
1249 * Returns the subset of the given locale list for which this locale list
1250 * has a matching (possibly fallback) locale. Locales appear in the same
1251 * order in the returned list as in the input list.
1252 * Options are optional parameter.
1254 %AddNamedProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) {
1255 if (%_IsConstructCall()) {
1256 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1259 return supportedLocalesOf('numberformat', locales, %_Arguments(1));
1263 %FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf');
1264 %FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf);
1265 %SetNativeFlag(Intl.NumberFormat.supportedLocalesOf);
1269 * Returns a String value representing the result of calling ToNumber(value)
1270 * according to the effective locale and the formatting options of this
1273 function formatNumber(formatter, value) {
1274 // Spec treats -0 and +0 as 0.
1275 var number = $Number(value) + 0;
1277 return %InternalNumberFormat(%GetImplFromInitializedIntlObject(formatter),
1283 * Returns a Number that represents string value that was passed in.
1285 function parseNumber(formatter, value) {
1286 return %InternalNumberParse(%GetImplFromInitializedIntlObject(formatter),
1287 GlobalString(value));
1291 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1);
1292 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1);
1295 * Returns a string that matches LDML representation of the options object.
1297 function toLDMLString(options) {
1298 var getOption = getGetOption(options, 'dateformat');
1300 var ldmlString = '';
1302 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']);
1303 ldmlString += appendToLDMLString(
1304 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'});
1306 option = getOption('era', 'string', ['narrow', 'short', 'long']);
1307 ldmlString += appendToLDMLString(
1308 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'});
1310 option = getOption('year', 'string', ['2-digit', 'numeric']);
1311 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'});
1313 option = getOption('month', 'string',
1314 ['2-digit', 'numeric', 'narrow', 'short', 'long']);
1315 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M',
1316 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'});
1318 option = getOption('day', 'string', ['2-digit', 'numeric']);
1319 ldmlString += appendToLDMLString(
1320 option, {'2-digit': 'dd', 'numeric': 'd'});
1322 var hr12 = getOption('hour12', 'boolean');
1323 option = getOption('hour', 'string', ['2-digit', 'numeric']);
1324 if (hr12 === undefined) {
1325 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'});
1326 } else if (hr12 === true) {
1327 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'});
1329 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'});
1332 option = getOption('minute', 'string', ['2-digit', 'numeric']);
1333 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'});
1335 option = getOption('second', 'string', ['2-digit', 'numeric']);
1336 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'});
1338 option = getOption('timeZoneName', 'string', ['short', 'long']);
1339 ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'});
1346 * Returns either LDML equivalent of the current option or empty string.
1348 function appendToLDMLString(option, pairs) {
1349 if (option !== undefined) {
1350 return pairs[option];
1358 * Returns object that matches LDML representation of the date.
1360 function fromLDMLString(ldmlString) {
1361 // First remove '' quoted text, so we lose 'Uhr' strings.
1362 ldmlString = ldmlString.replace(GetQuotedStringRE(), '');
1365 var match = ldmlString.match(/E{3,5}/g);
1366 options = appendToDateTimeObject(
1367 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'});
1369 match = ldmlString.match(/G{3,5}/g);
1370 options = appendToDateTimeObject(
1371 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'});
1373 match = ldmlString.match(/y{1,2}/g);
1374 options = appendToDateTimeObject(
1375 options, 'year', match, {y: 'numeric', yy: '2-digit'});
1377 match = ldmlString.match(/M{1,5}/g);
1378 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit',
1379 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'});
1381 // Sometimes we get L instead of M for month - standalone name.
1382 match = ldmlString.match(/L{1,5}/g);
1383 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit',
1384 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'});
1386 match = ldmlString.match(/d{1,2}/g);
1387 options = appendToDateTimeObject(
1388 options, 'day', match, {d: 'numeric', dd: '2-digit'});
1390 match = ldmlString.match(/h{1,2}/g);
1391 if (match !== null) {
1392 options['hour12'] = true;
1394 options = appendToDateTimeObject(
1395 options, 'hour', match, {h: 'numeric', hh: '2-digit'});
1397 match = ldmlString.match(/H{1,2}/g);
1398 if (match !== null) {
1399 options['hour12'] = false;
1401 options = appendToDateTimeObject(
1402 options, 'hour', match, {H: 'numeric', HH: '2-digit'});
1404 match = ldmlString.match(/m{1,2}/g);
1405 options = appendToDateTimeObject(
1406 options, 'minute', match, {m: 'numeric', mm: '2-digit'});
1408 match = ldmlString.match(/s{1,2}/g);
1409 options = appendToDateTimeObject(
1410 options, 'second', match, {s: 'numeric', ss: '2-digit'});
1412 match = ldmlString.match(/z|zzzz/g);
1413 options = appendToDateTimeObject(
1414 options, 'timeZoneName', match, {z: 'short', zzzz: 'long'});
1420 function appendToDateTimeObject(options, option, match, pairs) {
1421 if (IS_NULL(match)) {
1422 if (!options.hasOwnProperty(option)) {
1423 defineWEProperty(options, option, undefined);
1428 var property = match[0];
1429 defineWEProperty(options, option, pairs[property]);
1436 * Returns options with at least default values in it.
1438 function toDateTimeOptions(options, required, defaults) {
1439 if (options === undefined) {
1442 options = TO_OBJECT_INLINE(options);
1445 var needsDefault = true;
1446 if ((required === 'date' || required === 'any') &&
1447 (options.weekday !== undefined || options.year !== undefined ||
1448 options.month !== undefined || options.day !== undefined)) {
1449 needsDefault = false;
1452 if ((required === 'time' || required === 'any') &&
1453 (options.hour !== undefined || options.minute !== undefined ||
1454 options.second !== undefined)) {
1455 needsDefault = false;
1458 if (needsDefault && (defaults === 'date' || defaults === 'all')) {
1459 ObjectDefineProperty(options, 'year', {value: 'numeric',
1462 configurable: true});
1463 ObjectDefineProperty(options, 'month', {value: 'numeric',
1466 configurable: true});
1467 ObjectDefineProperty(options, 'day', {value: 'numeric',
1470 configurable: true});
1473 if (needsDefault && (defaults === 'time' || defaults === 'all')) {
1474 ObjectDefineProperty(options, 'hour', {value: 'numeric',
1477 configurable: true});
1478 ObjectDefineProperty(options, 'minute', {value: 'numeric',
1481 configurable: true});
1482 ObjectDefineProperty(options, 'second', {value: 'numeric',
1485 configurable: true});
1493 * Initializes the given object so it's a valid DateTimeFormat instance.
1494 * Useful for subclassing.
1496 function initializeDateTimeFormat(dateFormat, locales, options) {
1498 if (%IsInitializedIntlObject(dateFormat)) {
1499 throw new $TypeError('Trying to re-initialize DateTimeFormat object.');
1502 if (options === undefined) {
1506 var locale = resolveLocale('dateformat', locales, options);
1508 options = toDateTimeOptions(options, 'any', 'date');
1510 var getOption = getGetOption(options, 'dateformat');
1512 // We implement only best fit algorithm, but still need to check
1513 // if the formatMatcher values are in range.
1514 var matcher = getOption('formatMatcher', 'string',
1515 ['basic', 'best fit'], 'best fit');
1517 // Build LDML string for the skeleton that we pass to the formatter.
1518 var ldmlString = toLDMLString(options);
1520 // Filter out supported extension keys so we know what to put in resolved
1521 // section later on.
1522 // We need to pass calendar and number system to the method.
1523 var tz = canonicalizeTimeZoneID(options.timeZone);
1525 // ICU prefers options to be passed using -u- extension key/values, so
1526 // we need to build that.
1527 var internalOptions = {};
1528 var extensionMap = parseExtension(locale.extension);
1529 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP,
1530 getOption, internalOptions);
1532 var requestedLocale = locale.locale + extension;
1533 var resolved = ObjectDefineProperties({}, {
1534 calendar: {writable: true},
1535 day: {writable: true},
1536 era: {writable: true},
1537 hour12: {writable: true},
1538 hour: {writable: true},
1539 locale: {writable: true},
1540 minute: {writable: true},
1541 month: {writable: true},
1542 numberingSystem: {writable: true},
1543 pattern: {writable: true},
1544 requestedLocale: {value: requestedLocale, writable: true},
1545 second: {writable: true},
1546 timeZone: {writable: true},
1547 timeZoneName: {writable: true},
1548 tz: {value: tz, writable: true},
1549 weekday: {writable: true},
1550 year: {writable: true}
1553 var formatter = %CreateDateTimeFormat(
1554 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved);
1556 if (tz !== undefined && tz !== resolved.timeZone) {
1557 throw new $RangeError('Unsupported time zone specified ' + tz);
1560 %MarkAsInitializedIntlObjectOfType(dateFormat, 'dateformat', formatter);
1561 ObjectDefineProperty(dateFormat, 'resolved', {value: resolved});
1568 * Constructs Intl.DateTimeFormat object given optional locales and options
1573 %AddNamedProperty(Intl, 'DateTimeFormat', function() {
1574 var locales = %_Arguments(0);
1575 var options = %_Arguments(1);
1577 if (!this || this === Intl) {
1578 // Constructor is called as a function.
1579 return new Intl.DateTimeFormat(locales, options);
1582 return initializeDateTimeFormat(ToObject(this), locales, options);
1589 * DateTimeFormat resolvedOptions method.
1591 %AddNamedProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
1592 if (%_IsConstructCall()) {
1593 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1596 if (!%IsInitializedIntlObjectOfType(this, 'dateformat')) {
1597 throw new $TypeError('resolvedOptions method called on a non-object or ' +
1598 'on a object that is not Intl.DateTimeFormat.');
1602 var fromPattern = fromLDMLString(format.resolved.pattern);
1603 var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar];
1604 if (userCalendar === undefined) {
1605 // Use ICU name if we don't have a match. It shouldn't happen, but
1606 // it would be too strict to throw for this.
1607 userCalendar = format.resolved.calendar;
1610 var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1611 format.resolved.locale);
1615 numberingSystem: format.resolved.numberingSystem,
1616 calendar: userCalendar,
1617 timeZone: format.resolved.timeZone
1620 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName);
1621 addWECPropertyIfDefined(result, 'era', fromPattern.era);
1622 addWECPropertyIfDefined(result, 'year', fromPattern.year);
1623 addWECPropertyIfDefined(result, 'month', fromPattern.month);
1624 addWECPropertyIfDefined(result, 'day', fromPattern.day);
1625 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday);
1626 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12);
1627 addWECPropertyIfDefined(result, 'hour', fromPattern.hour);
1628 addWECPropertyIfDefined(result, 'minute', fromPattern.minute);
1629 addWECPropertyIfDefined(result, 'second', fromPattern.second);
1635 %FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions,
1637 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions);
1638 %SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions);
1642 * Returns the subset of the given locale list for which this locale list
1643 * has a matching (possibly fallback) locale. Locales appear in the same
1644 * order in the returned list as in the input list.
1645 * Options are optional parameter.
1647 %AddNamedProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) {
1648 if (%_IsConstructCall()) {
1649 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1652 return supportedLocalesOf('dateformat', locales, %_Arguments(1));
1656 %FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf');
1657 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf);
1658 %SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf);
1662 * Returns a String value representing the result of calling ToNumber(date)
1663 * according to the effective locale and the formatting options of this
1666 function formatDate(formatter, dateValue) {
1668 if (dateValue === undefined) {
1669 dateMs = GlobalDate.now();
1671 dateMs = $Number(dateValue);
1674 if (!$isFinite(dateMs)) {
1675 throw new $RangeError('Provided date is not in valid range.');
1678 return %InternalDateFormat(%GetImplFromInitializedIntlObject(formatter),
1679 new GlobalDate(dateMs));
1684 * Returns a Date object representing the result of calling ToString(value)
1685 * according to the effective locale and the formatting options of this
1687 * Returns undefined if date string cannot be parsed.
1689 function parseDate(formatter, value) {
1690 return %InternalDateParse(%GetImplFromInitializedIntlObject(formatter),
1691 GlobalString(value));
1695 // 0 because date is optional argument.
1696 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0);
1697 addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1);
1701 * Returns canonical Area/Location name, or throws an exception if the zone
1702 * name is invalid IANA name.
1704 function canonicalizeTimeZoneID(tzID) {
1705 // Skip undefined zones.
1706 if (tzID === undefined) {
1710 // Special case handling (UTC, GMT).
1711 var upperID = tzID.toUpperCase();
1712 if (upperID === 'UTC' || upperID === 'GMT' ||
1713 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') {
1717 // We expect only _ and / beside ASCII letters.
1718 // All inputs should conform to Area/Location from now on.
1719 var match = GetTimezoneNameCheckRE().exec(tzID);
1720 if (IS_NULL(match)) {
1721 throw new $RangeError('Expected Area/Location for time zone, got ' + tzID);
1724 var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]);
1726 while (match[i] !== undefined && i < match.length) {
1727 result = result + '_' + toTitleCaseWord(match[i]);
1735 * Initializes the given object so it's a valid BreakIterator instance.
1736 * Useful for subclassing.
1738 function initializeBreakIterator(iterator, locales, options) {
1739 if (%IsInitializedIntlObject(iterator)) {
1740 throw new $TypeError('Trying to re-initialize v8BreakIterator object.');
1743 if (options === undefined) {
1747 var getOption = getGetOption(options, 'breakiterator');
1749 var internalOptions = {};
1751 defineWEProperty(internalOptions, 'type', getOption(
1752 'type', 'string', ['character', 'word', 'sentence', 'line'], 'word'));
1754 var locale = resolveLocale('breakiterator', locales, options);
1755 var resolved = ObjectDefineProperties({}, {
1756 requestedLocale: {value: locale.locale, writable: true},
1757 type: {value: internalOptions.type, writable: true},
1758 locale: {writable: true}
1761 var internalIterator = %CreateBreakIterator(locale.locale,
1765 %MarkAsInitializedIntlObjectOfType(iterator, 'breakiterator',
1767 ObjectDefineProperty(iterator, 'resolved', {value: resolved});
1774 * Constructs Intl.v8BreakIterator object given optional locales and options
1779 %AddNamedProperty(Intl, 'v8BreakIterator', function() {
1780 var locales = %_Arguments(0);
1781 var options = %_Arguments(1);
1783 if (!this || this === Intl) {
1784 // Constructor is called as a function.
1785 return new Intl.v8BreakIterator(locales, options);
1788 return initializeBreakIterator(ToObject(this), locales, options);
1795 * BreakIterator resolvedOptions method.
1797 %AddNamedProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions',
1799 if (%_IsConstructCall()) {
1800 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1803 if (!%IsInitializedIntlObjectOfType(this, 'breakiterator')) {
1804 throw new $TypeError('resolvedOptions method called on a non-object or ' +
1805 'on a object that is not Intl.v8BreakIterator.');
1808 var segmenter = this;
1809 var locale = getOptimalLanguageTag(segmenter.resolved.requestedLocale,
1810 segmenter.resolved.locale);
1814 type: segmenter.resolved.type
1819 %FunctionSetName(Intl.v8BreakIterator.prototype.resolvedOptions,
1821 %FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions);
1822 %SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions);
1826 * Returns the subset of the given locale list for which this locale list
1827 * has a matching (possibly fallback) locale. Locales appear in the same
1828 * order in the returned list as in the input list.
1829 * Options are optional parameter.
1831 %AddNamedProperty(Intl.v8BreakIterator, 'supportedLocalesOf',
1833 if (%_IsConstructCall()) {
1834 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1837 return supportedLocalesOf('breakiterator', locales, %_Arguments(1));
1841 %FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf');
1842 %FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf);
1843 %SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf);
1847 * Adopts text to segment using the iterator. Old text, if present,
1850 function adoptText(iterator, text) {
1851 %BreakIteratorAdoptText(%GetImplFromInitializedIntlObject(iterator),
1852 GlobalString(text));
1857 * Returns index of the first break in the string and moves current pointer.
1859 function first(iterator) {
1860 return %BreakIteratorFirst(%GetImplFromInitializedIntlObject(iterator));
1865 * Returns the index of the next break and moves the pointer.
1867 function next(iterator) {
1868 return %BreakIteratorNext(%GetImplFromInitializedIntlObject(iterator));
1873 * Returns index of the current break.
1875 function current(iterator) {
1876 return %BreakIteratorCurrent(%GetImplFromInitializedIntlObject(iterator));
1881 * Returns type of the current break.
1883 function breakType(iterator) {
1884 return %BreakIteratorBreakType(%GetImplFromInitializedIntlObject(iterator));
1888 addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1);
1889 addBoundMethod(Intl.v8BreakIterator, 'first', first, 0);
1890 addBoundMethod(Intl.v8BreakIterator, 'next', next, 0);
1891 addBoundMethod(Intl.v8BreakIterator, 'current', current, 0);
1892 addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0);
1894 // Save references to Intl objects and methods we use, for added security.
1895 var savedObjects = {
1896 'collator': Intl.Collator,
1897 'numberformat': Intl.NumberFormat,
1898 'dateformatall': Intl.DateTimeFormat,
1899 'dateformatdate': Intl.DateTimeFormat,
1900 'dateformattime': Intl.DateTimeFormat
1904 // Default (created with undefined locales and options parameters) collator,
1905 // number and date format instances. They'll be created as needed.
1906 var defaultObjects = {
1907 'collator': undefined,
1908 'numberformat': undefined,
1909 'dateformatall': undefined,
1910 'dateformatdate': undefined,
1911 'dateformattime': undefined,
1916 * Returns cached or newly created instance of a given service.
1917 * We cache only default instances (where no locales or options are provided).
1919 function cachedOrNewService(service, locales, options, defaults) {
1920 var useOptions = (defaults === undefined) ? options : defaults;
1921 if (locales === undefined && options === undefined) {
1922 if (defaultObjects[service] === undefined) {
1923 defaultObjects[service] = new savedObjects[service](locales, useOptions);
1925 return defaultObjects[service];
1927 return new savedObjects[service](locales, useOptions);
1932 * Compares this and that, and returns less than 0, 0 or greater than 0 value.
1933 * Overrides the built-in method.
1935 OverrideFunction(GlobalString.prototype, 'localeCompare', function(that) {
1936 if (%_IsConstructCall()) {
1937 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1940 if (IS_NULL_OR_UNDEFINED(this)) {
1941 throw new $TypeError('Method invoked on undefined or null value.');
1944 var locales = %_Arguments(1);
1945 var options = %_Arguments(2);
1946 var collator = cachedOrNewService('collator', locales, options);
1947 return compare(collator, this, that);
1953 * Unicode normalization. This method is called with one argument that
1954 * specifies the normalization form.
1955 * If none is specified, "NFC" is assumed.
1956 * If the form is not one of "NFC", "NFD", "NFKC", or "NFKD", then throw
1957 * a RangeError Exception.
1959 OverrideFunction(GlobalString.prototype, 'normalize', function(that) {
1960 if (%_IsConstructCall()) {
1961 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1964 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
1966 var form = GlobalString(%_Arguments(0) || 'NFC');
1968 var normalizationForm = NORMALIZATION_FORMS.indexOf(form);
1969 if (normalizationForm === -1) {
1970 throw new $RangeError('The normalization form should be one of '
1971 + NORMALIZATION_FORMS.join(', ') + '.');
1974 return %StringNormalize(this, normalizationForm);
1980 * Formats a Number object (this) using locale and options values.
1981 * If locale or options are omitted, defaults are used.
1983 OverrideFunction($Number.prototype, 'toLocaleString', function() {
1984 if (%_IsConstructCall()) {
1985 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1988 if (!(this instanceof $Number) && typeof(this) !== 'number') {
1989 throw new $TypeError('Method invoked on an object that is not Number.');
1992 var locales = %_Arguments(0);
1993 var options = %_Arguments(1);
1994 var numberFormat = cachedOrNewService('numberformat', locales, options);
1995 return formatNumber(numberFormat, this);
2001 * Returns actual formatted date or fails if date parameter is invalid.
2003 function toLocaleDateTime(date, locales, options, required, defaults, service) {
2004 if (!(date instanceof GlobalDate)) {
2005 throw new $TypeError('Method invoked on an object that is not Date.');
2009 return 'Invalid Date';
2012 var internalOptions = toDateTimeOptions(options, required, defaults);
2015 cachedOrNewService(service, locales, options, internalOptions);
2017 return formatDate(dateFormat, date);
2022 * Formats a Date object (this) using locale and options values.
2023 * If locale or options are omitted, defaults are used - both date and time are
2024 * present in the output.
2026 OverrideFunction(GlobalDate.prototype, 'toLocaleString', function() {
2027 if (%_IsConstructCall()) {
2028 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2031 var locales = %_Arguments(0);
2032 var options = %_Arguments(1);
2033 return toLocaleDateTime(
2034 this, locales, options, 'any', 'all', 'dateformatall');
2040 * Formats a Date object (this) using locale and options values.
2041 * If locale or options are omitted, defaults are used - only date is present
2044 OverrideFunction(GlobalDate.prototype, 'toLocaleDateString', function() {
2045 if (%_IsConstructCall()) {
2046 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2049 var locales = %_Arguments(0);
2050 var options = %_Arguments(1);
2051 return toLocaleDateTime(
2052 this, locales, options, 'date', 'date', 'dateformatdate');
2058 * Formats a Date object (this) using locale and options values.
2059 * If locale or options are omitted, defaults are used - only time is present
2062 OverrideFunction(GlobalDate.prototype, 'toLocaleTimeString', function() {
2063 if (%_IsConstructCall()) {
2064 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2067 var locales = %_Arguments(0);
2068 var options = %_Arguments(1);
2069 return toLocaleDateTime(
2070 this, locales, options, 'time', 'time', 'dateformattime');