Home | History | Annotate | Download | only in src
      1 // Copyright 2013 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 "use strict";
      6 
      7 // ECMAScript 402 API implementation.
      8 
      9 /**
     10  * Intl object is a single object that has some named properties,
     11  * all of which are constructors.
     12  */
     13 $Object.defineProperty(global, "Intl", { enumerable: false, value: (function() {
     14 
     15 var Intl = {};
     16 
     17 var undefined = global.undefined;
     18 
     19 var AVAILABLE_SERVICES = ['collator',
     20                           'numberformat',
     21                           'dateformat',
     22                           'breakiterator'];
     23 
     24 var NORMALIZATION_FORMS = ['NFC',
     25                            'NFD',
     26                            'NFKC',
     27                            'NFKD'];
     28 
     29 /**
     30  * Caches available locales for each service.
     31  */
     32 var AVAILABLE_LOCALES = {
     33   'collator': undefined,
     34   'numberformat': undefined,
     35   'dateformat': undefined,
     36   'breakiterator': undefined
     37 };
     38 
     39 /**
     40  * Caches default ICU locale.
     41  */
     42 var DEFAULT_ICU_LOCALE = undefined;
     43 
     44 /**
     45  * Unicode extension regular expression.
     46  */
     47 var UNICODE_EXTENSION_RE = undefined;
     48 
     49 function GetUnicodeExtensionRE() {
     50   if (UNICODE_EXTENSION_RE === undefined) {
     51     UNICODE_EXTENSION_RE = new $RegExp('-u(-[a-z0-9]{2,8})+', 'g');
     52   }
     53   return UNICODE_EXTENSION_RE;
     54 }
     55 
     56 /**
     57  * Matches any Unicode extension.
     58  */
     59 var ANY_EXTENSION_RE = undefined;
     60 
     61 function GetAnyExtensionRE() {
     62   if (ANY_EXTENSION_RE === undefined) {
     63     ANY_EXTENSION_RE = new $RegExp('-[a-z0-9]{1}-.*', 'g');
     64   }
     65   return ANY_EXTENSION_RE;
     66 }
     67 
     68 /**
     69  * Replace quoted text (single quote, anything but the quote and quote again).
     70  */
     71 var QUOTED_STRING_RE = undefined;
     72 
     73 function GetQuotedStringRE() {
     74   if (QUOTED_STRING_RE === undefined) {
     75     QUOTED_STRING_RE = new $RegExp("'[^']+'", 'g');
     76   }
     77   return QUOTED_STRING_RE;
     78 }
     79 
     80 /**
     81  * Matches valid service name.
     82  */
     83 var SERVICE_RE = undefined;
     84 
     85 function GetServiceRE() {
     86   if (SERVICE_RE === undefined) {
     87     SERVICE_RE =
     88         new $RegExp('^(collator|numberformat|dateformat|breakiterator)$');
     89   }
     90   return SERVICE_RE;
     91 }
     92 
     93 /**
     94  * Validates a language tag against bcp47 spec.
     95  * Actual value is assigned on first run.
     96  */
     97 var LANGUAGE_TAG_RE = undefined;
     98 
     99 function GetLanguageTagRE() {
    100   if (LANGUAGE_TAG_RE === undefined) {
    101     BuildLanguageTagREs();
    102   }
    103   return LANGUAGE_TAG_RE;
    104 }
    105 
    106 /**
    107  * Helps find duplicate variants in the language tag.
    108  */
    109 var LANGUAGE_VARIANT_RE = undefined;
    110 
    111 function GetLanguageVariantRE() {
    112   if (LANGUAGE_VARIANT_RE === undefined) {
    113     BuildLanguageTagREs();
    114   }
    115   return LANGUAGE_VARIANT_RE;
    116 }
    117 
    118 /**
    119  * Helps find duplicate singletons in the language tag.
    120  */
    121 var LANGUAGE_SINGLETON_RE = undefined;
    122 
    123 function GetLanguageSingletonRE() {
    124   if (LANGUAGE_SINGLETON_RE === undefined) {
    125     BuildLanguageTagREs();
    126   }
    127   return LANGUAGE_SINGLETON_RE;
    128 }
    129 
    130 /**
    131  * Matches valid IANA time zone names.
    132  */
    133 var TIMEZONE_NAME_CHECK_RE = undefined;
    134 
    135 function GetTimezoneNameCheckRE() {
    136   if (TIMEZONE_NAME_CHECK_RE === undefined) {
    137     TIMEZONE_NAME_CHECK_RE =
    138         new $RegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$');
    139   }
    140   return TIMEZONE_NAME_CHECK_RE;
    141 }
    142 
    143 /**
    144  * Maps ICU calendar names into LDML type.
    145  */
    146 var ICU_CALENDAR_MAP = {
    147   'gregorian': 'gregory',
    148   'japanese': 'japanese',
    149   'buddhist': 'buddhist',
    150   'roc': 'roc',
    151   'persian': 'persian',
    152   'islamic-civil': 'islamicc',
    153   'islamic': 'islamic',
    154   'hebrew': 'hebrew',
    155   'chinese': 'chinese',
    156   'indian': 'indian',
    157   'coptic': 'coptic',
    158   'ethiopic': 'ethiopic',
    159   'ethiopic-amete-alem': 'ethioaa'
    160 };
    161 
    162 /**
    163  * Map of Unicode extensions to option properties, and their values and types,
    164  * for a collator.
    165  */
    166 var COLLATOR_KEY_MAP = {
    167   'kn': {'property': 'numeric', 'type': 'boolean'},
    168   'kf': {'property': 'caseFirst', 'type': 'string',
    169          'values': ['false', 'lower', 'upper']}
    170 };
    171 
    172 /**
    173  * Map of Unicode extensions to option properties, and their values and types,
    174  * for a number format.
    175  */
    176 var NUMBER_FORMAT_KEY_MAP = {
    177   'nu': {'property': undefined, 'type': 'string'}
    178 };
    179 
    180 /**
    181  * Map of Unicode extensions to option properties, and their values and types,
    182  * for a date/time format.
    183  */
    184 var DATETIME_FORMAT_KEY_MAP = {
    185   'ca': {'property': undefined, 'type': 'string'},
    186   'nu': {'property': undefined, 'type': 'string'}
    187 };
    188 
    189 /**
    190  * Allowed -u-co- values. List taken from:
    191  * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml
    192  */
    193 var ALLOWED_CO_VALUES = [
    194   'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic',
    195   'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin'
    196 ];
    197 
    198 /**
    199  * Error message for when function object is created with new and it's not
    200  * a constructor.
    201  */
    202 var ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR =
    203   'Function object that\'s not a constructor was created with new';
    204 
    205 
    206 /**
    207  * Adds bound method to the prototype of the given object.
    208  */
    209 function addBoundMethod(obj, methodName, implementation, length) {
    210   function getter() {
    211     if (!%IsInitializedIntlObject(this)) {
    212         throw new $TypeError('Method ' + methodName + ' called on a ' +
    213                             'non-object or on a wrong type of object.');
    214     }
    215     var internalName = '__bound' + methodName + '__';
    216     if (this[internalName] === undefined) {
    217       var that = this;
    218       var boundMethod;
    219       if (length === undefined || length === 2) {
    220         boundMethod = function(x, y) {
    221           if (%_IsConstructCall()) {
    222             throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
    223           }
    224           return implementation(that, x, y);
    225         }
    226       } else if (length === 1) {
    227         boundMethod = function(x) {
    228           if (%_IsConstructCall()) {
    229             throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
    230           }
    231           return implementation(that, x);
    232         }
    233       } else {
    234         boundMethod = function() {
    235           if (%_IsConstructCall()) {
    236             throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
    237           }
    238           // DateTimeFormat.format needs to be 0 arg method, but can stil
    239           // receive optional dateValue param. If one was provided, pass it
    240           // along.
    241           if (%_ArgumentsLength() > 0) {
    242             return implementation(that, %_Arguments(0));
    243           } else {
    244             return implementation(that);
    245           }
    246         }
    247       }
    248       %FunctionSetName(boundMethod, internalName);
    249       %FunctionRemovePrototype(boundMethod);
    250       %SetNativeFlag(boundMethod);
    251       this[internalName] = boundMethod;
    252     }
    253     return this[internalName];
    254   }
    255 
    256   %FunctionSetName(getter, methodName);
    257   %FunctionRemovePrototype(getter);
    258   %SetNativeFlag(getter);
    259 
    260   ObjectDefineProperty(obj.prototype, methodName, {
    261     get: getter,
    262     enumerable: false,
    263     configurable: true
    264   });
    265 }
    266 
    267 
    268 /**
    269  * Returns an intersection of locales and service supported locales.
    270  * Parameter locales is treated as a priority list.
    271  */
    272 function supportedLocalesOf(service, locales, options) {
    273   if (IS_NULL(service.match(GetServiceRE()))) {
    274     throw new $Error('Internal error, wrong service type: ' + service);
    275   }
    276 
    277   // Provide defaults if matcher was not specified.
    278   if (options === undefined) {
    279     options = {};
    280   } else {
    281     options = ToObject(options);
    282   }
    283 
    284   var matcher = options.localeMatcher;
    285   if (matcher !== undefined) {
    286     matcher = $String(matcher);
    287     if (matcher !== 'lookup' && matcher !== 'best fit') {
    288       throw new $RangeError('Illegal value for localeMatcher:' + matcher);
    289     }
    290   } else {
    291     matcher = 'best fit';
    292   }
    293 
    294   var requestedLocales = initializeLocaleList(locales);
    295 
    296   // Cache these, they don't ever change per service.
    297   if (AVAILABLE_LOCALES[service] === undefined) {
    298     AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
    299   }
    300 
    301   // Use either best fit or lookup algorithm to match locales.
    302   if (matcher === 'best fit') {
    303     return initializeLocaleList(bestFitSupportedLocalesOf(
    304         requestedLocales, AVAILABLE_LOCALES[service]));
    305   }
    306 
    307   return initializeLocaleList(lookupSupportedLocalesOf(
    308       requestedLocales, AVAILABLE_LOCALES[service]));
    309 }
    310 
    311 
    312 /**
    313  * Returns the subset of the provided BCP 47 language priority list for which
    314  * this service has a matching locale when using the BCP 47 Lookup algorithm.
    315  * Locales appear in the same order in the returned list as in the input list.
    316  */
    317 function lookupSupportedLocalesOf(requestedLocales, availableLocales) {
    318   var matchedLocales = [];
    319   for (var i = 0; i < requestedLocales.length; ++i) {
    320     // Remove -u- extension.
    321     var locale = requestedLocales[i].replace(GetUnicodeExtensionRE(), '');
    322     do {
    323       if (availableLocales[locale] !== undefined) {
    324         // Push requested locale not the resolved one.
    325         matchedLocales.push(requestedLocales[i]);
    326         break;
    327       }
    328       // Truncate locale if possible, if not break.
    329       var pos = locale.lastIndexOf('-');
    330       if (pos === -1) {
    331         break;
    332       }
    333       locale = locale.substring(0, pos);
    334     } while (true);
    335   }
    336 
    337   return matchedLocales;
    338 }
    339 
    340 
    341 /**
    342  * Returns the subset of the provided BCP 47 language priority list for which
    343  * this service has a matching locale when using the implementation
    344  * dependent algorithm.
    345  * Locales appear in the same order in the returned list as in the input list.
    346  */
    347 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) {
    348   // TODO(cira): implement better best fit algorithm.
    349   return lookupSupportedLocalesOf(requestedLocales, availableLocales);
    350 }
    351 
    352 
    353 /**
    354  * Returns a getOption function that extracts property value for given
    355  * options object. If property is missing it returns defaultValue. If value
    356  * is out of range for that property it throws RangeError.
    357  */
    358 function getGetOption(options, caller) {
    359   if (options === undefined) {
    360     throw new $Error('Internal ' + caller + ' error. ' +
    361                     'Default options are missing.');
    362   }
    363 
    364   var getOption = function getOption(property, type, values, defaultValue) {
    365     if (options[property] !== undefined) {
    366       var value = options[property];
    367       switch (type) {
    368         case 'boolean':
    369           value = $Boolean(value);
    370           break;
    371         case 'string':
    372           value = $String(value);
    373           break;
    374         case 'number':
    375           value = $Number(value);
    376           break;
    377         default:
    378           throw new $Error('Internal error. Wrong value type.');
    379       }
    380       if (values !== undefined && values.indexOf(value) === -1) {
    381         throw new $RangeError('Value ' + value + ' out of range for ' + caller +
    382                              ' options property ' + property);
    383       }
    384 
    385       return value;
    386     }
    387 
    388     return defaultValue;
    389   }
    390 
    391   return getOption;
    392 }
    393 
    394 
    395 /**
    396  * Compares a BCP 47 language priority list requestedLocales against the locales
    397  * in availableLocales and determines the best available language to meet the
    398  * request. Two algorithms are available to match the locales: the Lookup
    399  * algorithm described in RFC 4647 section 3.4, and an implementation dependent
    400  * best-fit algorithm. Independent of the locale matching algorithm, options
    401  * specified through Unicode locale extension sequences are negotiated
    402  * separately, taking the caller's relevant extension keys and locale data as
    403  * well as client-provided options into consideration. Returns an object with
    404  * a locale property whose value is the language tag of the selected locale,
    405  * and properties for each key in relevantExtensionKeys providing the selected
    406  * value for that key.
    407  */
    408 function resolveLocale(service, requestedLocales, options) {
    409   requestedLocales = initializeLocaleList(requestedLocales);
    410 
    411   var getOption = getGetOption(options, service);
    412   var matcher = getOption('localeMatcher', 'string',
    413                           ['lookup', 'best fit'], 'best fit');
    414   var resolved;
    415   if (matcher === 'lookup') {
    416     resolved = lookupMatcher(service, requestedLocales);
    417   } else {
    418     resolved = bestFitMatcher(service, requestedLocales);
    419   }
    420 
    421   return resolved;
    422 }
    423 
    424 
    425 /**
    426  * Returns best matched supported locale and extension info using basic
    427  * lookup algorithm.
    428  */
    429 function lookupMatcher(service, requestedLocales) {
    430   if (IS_NULL(service.match(GetServiceRE()))) {
    431     throw new $Error('Internal error, wrong service type: ' + service);
    432   }
    433 
    434   // Cache these, they don't ever change per service.
    435   if (AVAILABLE_LOCALES[service] === undefined) {
    436     AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
    437   }
    438 
    439   for (var i = 0; i < requestedLocales.length; ++i) {
    440     // Remove all extensions.
    441     var locale = requestedLocales[i].replace(GetAnyExtensionRE(), '');
    442     do {
    443       if (AVAILABLE_LOCALES[service][locale] !== undefined) {
    444         // Return the resolved locale and extension.
    445         var extensionMatch = requestedLocales[i].match(GetUnicodeExtensionRE());
    446         var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0];
    447         return {'locale': locale, 'extension': extension, 'position': i};
    448       }
    449       // Truncate locale if possible.
    450       var pos = locale.lastIndexOf('-');
    451       if (pos === -1) {
    452         break;
    453       }
    454       locale = locale.substring(0, pos);
    455     } while (true);
    456   }
    457 
    458   // Didn't find a match, return default.
    459   if (DEFAULT_ICU_LOCALE === undefined) {
    460     DEFAULT_ICU_LOCALE = %GetDefaultICULocale();
    461   }
    462 
    463   return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1};
    464 }
    465 
    466 
    467 /**
    468  * Returns best matched supported locale and extension info using
    469  * implementation dependend algorithm.
    470  */
    471 function bestFitMatcher(service, requestedLocales) {
    472   // TODO(cira): implement better best fit algorithm.
    473   return lookupMatcher(service, requestedLocales);
    474 }
    475 
    476 
    477 /**
    478  * Parses Unicode extension into key - value map.
    479  * Returns empty object if the extension string is invalid.
    480  * We are not concerned with the validity of the values at this point.
    481  */
    482 function parseExtension(extension) {
    483   var extensionSplit = extension.split('-');
    484 
    485   // Assume ['', 'u', ...] input, but don't throw.
    486   if (extensionSplit.length <= 2 ||
    487       (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
    488     return {};
    489   }
    490 
    491   // Key is {2}alphanum, value is {3,8}alphanum.
    492   // Some keys may not have explicit values (booleans).
    493   var extensionMap = {};
    494   var previousKey = undefined;
    495   for (var i = 2; i < extensionSplit.length; ++i) {
    496     var length = extensionSplit[i].length;
    497     var element = extensionSplit[i];
    498     if (length === 2) {
    499       extensionMap[element] = undefined;
    500       previousKey = element;
    501     } else if (length >= 3 && length <=8 && previousKey !== undefined) {
    502       extensionMap[previousKey] = element;
    503       previousKey = undefined;
    504     } else {
    505       // There is a value that's too long, or that doesn't have a key.
    506       return {};
    507     }
    508   }
    509 
    510   return extensionMap;
    511 }
    512 
    513 
    514 /**
    515  * Populates internalOptions object with boolean key-value pairs
    516  * from extensionMap and options.
    517  * Returns filtered extension (number and date format constructors use
    518  * Unicode extensions for passing parameters to ICU).
    519  * It's used for extension-option pairs only, e.g. kn-normalization, but not
    520  * for 'sensitivity' since it doesn't have extension equivalent.
    521  * Extensions like nu and ca don't have options equivalent, so we place
    522  * undefined in the map.property to denote that.
    523  */
    524 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
    525   var extension = '';
    526 
    527   var updateExtension = function updateExtension(key, value) {
    528     return '-' + key + '-' + $String(value);
    529   }
    530 
    531   var updateProperty = function updateProperty(property, type, value) {
    532     if (type === 'boolean' && (typeof value === 'string')) {
    533       value = (value === 'true') ? true : false;
    534     }
    535 
    536     if (property !== undefined) {
    537       defineWEProperty(outOptions, property, value);
    538     }
    539   }
    540 
    541   for (var key in keyValues) {
    542     if (keyValues.hasOwnProperty(key)) {
    543       var value = undefined;
    544       var map = keyValues[key];
    545       if (map.property !== undefined) {
    546         // This may return true if user specifies numeric: 'false', since
    547         // Boolean('nonempty') === true.
    548         value = getOption(map.property, map.type, map.values);
    549       }
    550       if (value !== undefined) {
    551         updateProperty(map.property, map.type, value);
    552         extension += updateExtension(key, value);
    553         continue;
    554       }
    555       // User options didn't have it, check Unicode extension.
    556       // Here we want to convert strings 'true', 'false' into proper Boolean
    557       // values (not a user error).
    558       if (extensionMap.hasOwnProperty(key)) {
    559         value = extensionMap[key];
    560         if (value !== undefined) {
    561           updateProperty(map.property, map.type, value);
    562           extension += updateExtension(key, value);
    563         } else if (map.type === 'boolean') {
    564           // Boolean keys are allowed not to have values in Unicode extension.
    565           // Those default to true.
    566           updateProperty(map.property, map.type, true);
    567           extension += updateExtension(key, true);
    568         }
    569       }
    570     }
    571   }
    572 
    573   return extension === ''? '' : '-u' + extension;
    574 }
    575 
    576 
    577 /**
    578  * Converts all OwnProperties into
    579  * configurable: false, writable: false, enumerable: true.
    580  */
    581 function freezeArray(array) {
    582   array.forEach(function(element, index) {
    583     ObjectDefineProperty(array, index, {value: element,
    584                                           configurable: false,
    585                                           writable: false,
    586                                           enumerable: true});
    587   });
    588 
    589   ObjectDefineProperty(array, 'length', {value: array.length,
    590                                          writable: false});
    591   return array;
    592 }
    593 
    594 
    595 /**
    596  * It's sometimes desireable to leave user requested locale instead of ICU
    597  * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
    598  * one, if that was what user requested).
    599  * This function returns user specified tag if its maximized form matches ICU
    600  * resolved locale. If not we return ICU result.
    601  */
    602 function getOptimalLanguageTag(original, resolved) {
    603   // Returns Array<Object>, where each object has maximized and base properties.
    604   // Maximized: zh -> zh-Hans-CN
    605   // Base: zh-CN-u-ca-gregory -> zh-CN
    606   // Take care of grandfathered or simple cases.
    607   if (original === resolved) {
    608     return original;
    609   }
    610 
    611   var locales = %GetLanguageTagVariants([original, resolved]);
    612   if (locales[0].maximized !== locales[1].maximized) {
    613     return resolved;
    614   }
    615 
    616   // Preserve extensions of resolved locale, but swap base tags with original.
    617   var resolvedBase = new $RegExp('^' + locales[1].base);
    618   return resolved.replace(resolvedBase, locales[0].base);
    619 }
    620 
    621 
    622 /**
    623  * Returns an Object that contains all of supported locales for a given
    624  * service.
    625  * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ
    626  * that is supported. This is required by the spec.
    627  */
    628 function getAvailableLocalesOf(service) {
    629   var available = %AvailableLocalesOf(service);
    630 
    631   for (var i in available) {
    632     if (available.hasOwnProperty(i)) {
    633       var parts = i.match(/^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/);
    634       if (parts !== null) {
    635         // Build xx-ZZ. We don't care about the actual value,
    636         // as long it's not undefined.
    637         available[parts[1] + '-' + parts[3]] = null;
    638       }
    639     }
    640   }
    641 
    642   return available;
    643 }
    644 
    645 
    646 /**
    647  * Defines a property and sets writable and enumerable to true.
    648  * Configurable is false by default.
    649  */
    650 function defineWEProperty(object, property, value) {
    651   ObjectDefineProperty(object, property,
    652                        {value: value, writable: true, enumerable: true});
    653 }
    654 
    655 
    656 /**
    657  * Adds property to an object if the value is not undefined.
    658  * Sets configurable descriptor to false.
    659  */
    660 function addWEPropertyIfDefined(object, property, value) {
    661   if (value !== undefined) {
    662     defineWEProperty(object, property, value);
    663   }
    664 }
    665 
    666 
    667 /**
    668  * Defines a property and sets writable, enumerable and configurable to true.
    669  */
    670 function defineWECProperty(object, property, value) {
    671   ObjectDefineProperty(object, property,
    672                        {value: value,
    673                         writable: true,
    674                         enumerable: true,
    675                         configurable: true});
    676 }
    677 
    678 
    679 /**
    680  * Adds property to an object if the value is not undefined.
    681  * Sets all descriptors to true.
    682  */
    683 function addWECPropertyIfDefined(object, property, value) {
    684   if (value !== undefined) {
    685     defineWECProperty(object, property, value);
    686   }
    687 }
    688 
    689 
    690 /**
    691  * Returns titlecased word, aMeRricA -> America.
    692  */
    693 function toTitleCaseWord(word) {
    694   return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase();
    695 }
    696 
    697 /**
    698  * Canonicalizes the language tag, or throws in case the tag is invalid.
    699  */
    700 function canonicalizeLanguageTag(localeID) {
    701   // null is typeof 'object' so we have to do extra check.
    702   if (typeof localeID !== 'string' && typeof localeID !== 'object' ||
    703       IS_NULL(localeID)) {
    704     throw new $TypeError('Language ID should be string or object.');
    705   }
    706 
    707   var localeString = $String(localeID);
    708 
    709   if (isValidLanguageTag(localeString) === false) {
    710     throw new $RangeError('Invalid language tag: ' + localeString);
    711   }
    712 
    713   // This call will strip -kn but not -kn-true extensions.
    714   // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265.
    715   // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after
    716   // upgrade to ICU 4.9.
    717   var tag = %CanonicalizeLanguageTag(localeString);
    718   if (tag === 'invalid-tag') {
    719     throw new $RangeError('Invalid language tag: ' + localeString);
    720   }
    721 
    722   return tag;
    723 }
    724 
    725 
    726 /**
    727  * Returns an array where all locales are canonicalized and duplicates removed.
    728  * Throws on locales that are not well formed BCP47 tags.
    729  */
    730 function initializeLocaleList(locales) {
    731   var seen = [];
    732   if (locales === undefined) {
    733     // Constructor is called without arguments.
    734     seen = [];
    735   } else {
    736     // We allow single string localeID.
    737     if (typeof locales === 'string') {
    738       seen.push(canonicalizeLanguageTag(locales));
    739       return freezeArray(seen);
    740     }
    741 
    742     var o = ToObject(locales);
    743     // Converts it to UInt32 (>>> is shr on 32bit integers).
    744     var len = o.length >>> 0;
    745 
    746     for (var k = 0; k < len; k++) {
    747       if (k in o) {
    748         var value = o[k];
    749 
    750         var tag = canonicalizeLanguageTag(value);
    751 
    752         if (seen.indexOf(tag) === -1) {
    753           seen.push(tag);
    754         }
    755       }
    756     }
    757   }
    758 
    759   return freezeArray(seen);
    760 }
    761 
    762 
    763 /**
    764  * Validates the language tag. Section 2.2.9 of the bcp47 spec
    765  * defines a valid tag.
    766  *
    767  * ICU is too permissible and lets invalid tags, like
    768  * hant-cmn-cn, through.
    769  *
    770  * Returns false if the language tag is invalid.
    771  */
    772 function isValidLanguageTag(locale) {
    773   // Check if it's well-formed, including grandfadered tags.
    774   if (GetLanguageTagRE().test(locale) === false) {
    775     return false;
    776   }
    777 
    778   // Just return if it's a x- form. It's all private.
    779   if (locale.indexOf('x-') === 0) {
    780     return true;
    781   }
    782 
    783   // Check if there are any duplicate variants or singletons (extensions).
    784 
    785   // Remove private use section.
    786   locale = locale.split(/-x-/)[0];
    787 
    788   // Skip language since it can match variant regex, so we start from 1.
    789   // We are matching i-klingon here, but that's ok, since i-klingon-klingon
    790   // is not valid and would fail LANGUAGE_TAG_RE test.
    791   var variants = [];
    792   var extensions = [];
    793   var parts = locale.split(/-/);
    794   for (var i = 1; i < parts.length; i++) {
    795     var value = parts[i];
    796     if (GetLanguageVariantRE().test(value) === true && extensions.length === 0) {
    797       if (variants.indexOf(value) === -1) {
    798         variants.push(value);
    799       } else {
    800         return false;
    801       }
    802     }
    803 
    804     if (GetLanguageSingletonRE().test(value) === true) {
    805       if (extensions.indexOf(value) === -1) {
    806         extensions.push(value);
    807       } else {
    808         return false;
    809       }
    810     }
    811   }
    812 
    813   return true;
    814  }
    815 
    816 
    817 /**
    818  * Builds a regular expresion that validates the language tag
    819  * against bcp47 spec.
    820  * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF.
    821  * Runs on load and initializes the global REs.
    822  */
    823 function BuildLanguageTagREs() {
    824   var alpha = '[a-zA-Z]';
    825   var digit = '[0-9]';
    826   var alphanum = '(' + alpha + '|' + digit + ')';
    827   var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' +
    828                 'zh-min|zh-min-nan|zh-xiang)';
    829   var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' +
    830                   'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' +
    831                   'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)';
    832   var grandfathered = '(' + irregular + '|' + regular + ')';
    833   var privateUse = '(x(-' + alphanum + '{1,8})+)';
    834 
    835   var singleton = '(' + digit + '|[A-WY-Za-wy-z])';
    836   LANGUAGE_SINGLETON_RE = new $RegExp('^' + singleton + '$', 'i');
    837 
    838   var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)';
    839 
    840   var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))';
    841   LANGUAGE_VARIANT_RE = new $RegExp('^' + variant + '$', 'i');
    842 
    843   var region = '(' + alpha + '{2}|' + digit + '{3})';
    844   var script = '(' + alpha + '{4})';
    845   var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})';
    846   var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' +
    847                  alpha + '{5,8})';
    848   var langTag = language + '(-' + script + ')?(-' + region + ')?(-' +
    849                 variant + ')*(-' + extension + ')*(-' + privateUse + ')?';
    850 
    851   var languageTag =
    852       '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$';
    853   LANGUAGE_TAG_RE = new $RegExp(languageTag, 'i');
    854 }
    855 
    856 /**
    857  * Initializes the given object so it's a valid Collator instance.
    858  * Useful for subclassing.
    859  */
    860 function initializeCollator(collator, locales, options) {
    861   if (%IsInitializedIntlObject(collator)) {
    862     throw new $TypeError('Trying to re-initialize Collator object.');
    863   }
    864 
    865   if (options === undefined) {
    866     options = {};
    867   }
    868 
    869   var getOption = getGetOption(options, 'collator');
    870 
    871   var internalOptions = {};
    872 
    873   defineWEProperty(internalOptions, 'usage', getOption(
    874     'usage', 'string', ['sort', 'search'], 'sort'));
    875 
    876   var sensitivity = getOption('sensitivity', 'string',
    877                               ['base', 'accent', 'case', 'variant']);
    878   if (sensitivity === undefined && internalOptions.usage === 'sort') {
    879     sensitivity = 'variant';
    880   }
    881   defineWEProperty(internalOptions, 'sensitivity', sensitivity);
    882 
    883   defineWEProperty(internalOptions, 'ignorePunctuation', getOption(
    884     'ignorePunctuation', 'boolean', undefined, false));
    885 
    886   var locale = resolveLocale('collator', locales, options);
    887 
    888   // ICU can't take kb, kc... parameters through localeID, so we need to pass
    889   // them as options.
    890   // One exception is -co- which has to be part of the extension, but only for
    891   // usage: sort, and its value can't be 'standard' or 'search'.
    892   var extensionMap = parseExtension(locale.extension);
    893   setOptions(
    894       options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions);
    895 
    896   var collation = 'default';
    897   var extension = '';
    898   if (extensionMap.hasOwnProperty('co') && internalOptions.usage === 'sort') {
    899     if (ALLOWED_CO_VALUES.indexOf(extensionMap.co) !== -1) {
    900       extension = '-u-co-' + extensionMap.co;
    901       // ICU can't tell us what the collation is, so save user's input.
    902       collation = extensionMap.co;
    903     }
    904   } else if (internalOptions.usage === 'search') {
    905     extension = '-u-co-search';
    906   }
    907   defineWEProperty(internalOptions, 'collation', collation);
    908 
    909   var requestedLocale = locale.locale + extension;
    910 
    911   // We define all properties C++ code may produce, to prevent security
    912   // problems. If malicious user decides to redefine Object.prototype.locale
    913   // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us").
    914   // ObjectDefineProperties will either succeed defining or throw an error.
    915   var resolved = ObjectDefineProperties({}, {
    916     caseFirst: {writable: true},
    917     collation: {value: internalOptions.collation, writable: true},
    918     ignorePunctuation: {writable: true},
    919     locale: {writable: true},
    920     numeric: {writable: true},
    921     requestedLocale: {value: requestedLocale, writable: true},
    922     sensitivity: {writable: true},
    923     strength: {writable: true},
    924     usage: {value: internalOptions.usage, writable: true}
    925   });
    926 
    927   var internalCollator = %CreateCollator(requestedLocale,
    928                                          internalOptions,
    929                                          resolved);
    930 
    931   // Writable, configurable and enumerable are set to false by default.
    932   %MarkAsInitializedIntlObjectOfType(collator, 'collator', internalCollator);
    933   ObjectDefineProperty(collator, 'resolved', {value: resolved});
    934 
    935   return collator;
    936 }
    937 
    938 
    939 /**
    940  * Constructs Intl.Collator object given optional locales and options
    941  * parameters.
    942  *
    943  * @constructor
    944  */
    945 %AddNamedProperty(Intl, 'Collator', function() {
    946     var locales = %_Arguments(0);
    947     var options = %_Arguments(1);
    948 
    949     if (!this || this === Intl) {
    950       // Constructor is called as a function.
    951       return new Intl.Collator(locales, options);
    952     }
    953 
    954     return initializeCollator(ToObject(this), locales, options);
    955   },
    956   DONT_ENUM
    957 );
    958 
    959 
    960 /**
    961  * Collator resolvedOptions method.
    962  */
    963 %AddNamedProperty(Intl.Collator.prototype, 'resolvedOptions', function() {
    964     if (%_IsConstructCall()) {
    965       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
    966     }
    967 
    968     if (!%IsInitializedIntlObjectOfType(this, 'collator')) {
    969       throw new $TypeError('resolvedOptions method called on a non-object ' +
    970                            'or on a object that is not Intl.Collator.');
    971     }
    972 
    973     var coll = this;
    974     var locale = getOptimalLanguageTag(coll.resolved.requestedLocale,
    975                                        coll.resolved.locale);
    976 
    977     return {
    978       locale: 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
    985     };
    986   },
    987   DONT_ENUM
    988 );
    989 %FunctionSetName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions');
    990 %FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions);
    991 %SetNativeFlag(Intl.Collator.prototype.resolvedOptions);
    992 
    993 
    994 /**
    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.
    999  */
   1000 %AddNamedProperty(Intl.Collator, 'supportedLocalesOf', function(locales) {
   1001     if (%_IsConstructCall()) {
   1002       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1003     }
   1004 
   1005     return supportedLocalesOf('collator', locales, %_Arguments(1));
   1006   },
   1007   DONT_ENUM
   1008 );
   1009 %FunctionSetName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf');
   1010 %FunctionRemovePrototype(Intl.Collator.supportedLocalesOf);
   1011 %SetNativeFlag(Intl.Collator.supportedLocalesOf);
   1012 
   1013 
   1014 /**
   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.
   1023  */
   1024 function compare(collator, x, y) {
   1025   return %InternalCompare(%GetImplFromInitializedIntlObject(collator),
   1026                           $String(x), $String(y));
   1027 };
   1028 
   1029 
   1030 addBoundMethod(Intl.Collator, 'compare', compare, 2);
   1031 
   1032 /**
   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.
   1036  */
   1037 function isWellFormedCurrencyCode(currency) {
   1038   return typeof currency == "string" &&
   1039       currency.length == 3 &&
   1040       currency.match(/[^A-Za-z]/) == null;
   1041 }
   1042 
   1043 
   1044 /**
   1045  * Returns the valid digit count for a property, or throws RangeError on
   1046  * a value out of the range.
   1047  */
   1048 function getNumberOption(options, property, min, max, fallback) {
   1049   var value = options[property];
   1050   if (value !== undefined) {
   1051     value = $Number(value);
   1052     if ($isNaN(value) || value < min || value > max) {
   1053       throw new $RangeError(property + ' value is out of range.');
   1054     }
   1055     return $floor(value);
   1056   }
   1057 
   1058   return fallback;
   1059 }
   1060 
   1061 
   1062 /**
   1063  * Initializes the given object so it's a valid NumberFormat instance.
   1064  * Useful for subclassing.
   1065  */
   1066 function initializeNumberFormat(numberFormat, locales, options) {
   1067   if (%IsInitializedIntlObject(numberFormat)) {
   1068     throw new $TypeError('Trying to re-initialize NumberFormat object.');
   1069   }
   1070 
   1071   if (options === undefined) {
   1072     options = {};
   1073   }
   1074 
   1075   var getOption = getGetOption(options, 'numberformat');
   1076 
   1077   var locale = resolveLocale('numberformat', locales, options);
   1078 
   1079   var internalOptions = {};
   1080   defineWEProperty(internalOptions, 'style', getOption(
   1081     'style', 'string', ['decimal', 'percent', 'currency'], 'decimal'));
   1082 
   1083   var currency = getOption('currency', 'string');
   1084   if (currency !== undefined && !isWellFormedCurrencyCode(currency)) {
   1085     throw new $RangeError('Invalid currency code: ' + currency);
   1086   }
   1087 
   1088   if (internalOptions.style === 'currency' && currency === undefined) {
   1089     throw new $TypeError('Currency code is required with currency style.');
   1090   }
   1091 
   1092   var currencyDisplay = getOption(
   1093       'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
   1094   if (internalOptions.style === 'currency') {
   1095     defineWEProperty(internalOptions, 'currency', currency.toUpperCase());
   1096     defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay);
   1097   }
   1098 
   1099   // Digit ranges.
   1100   var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
   1101   defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid);
   1102 
   1103   var mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0);
   1104   defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd);
   1105 
   1106   var mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, 3);
   1107   defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd);
   1108 
   1109   var mnsd = options['minimumSignificantDigits'];
   1110   var mxsd = options['maximumSignificantDigits'];
   1111   if (mnsd !== undefined || mxsd !== undefined) {
   1112     mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0);
   1113     defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd);
   1114 
   1115     mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21);
   1116     defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd);
   1117   }
   1118 
   1119   // Grouping.
   1120   defineWEProperty(internalOptions, 'useGrouping', getOption(
   1121     'useGrouping', 'boolean', undefined, true));
   1122 
   1123   // ICU prefers options to be passed using -u- extension key/values for
   1124   // number format, so we need to build that.
   1125   var extensionMap = parseExtension(locale.extension);
   1126   var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP,
   1127                              getOption, internalOptions);
   1128 
   1129   var requestedLocale = locale.locale + extension;
   1130   var resolved = ObjectDefineProperties({}, {
   1131     currency: {writable: true},
   1132     currencyDisplay: {writable: true},
   1133     locale: {writable: true},
   1134     maximumFractionDigits: {writable: true},
   1135     minimumFractionDigits: {writable: true},
   1136     minimumIntegerDigits: {writable: true},
   1137     numberingSystem: {writable: true},
   1138     requestedLocale: {value: requestedLocale, writable: true},
   1139     style: {value: internalOptions.style, writable: true},
   1140     useGrouping: {writable: true}
   1141   });
   1142   if (internalOptions.hasOwnProperty('minimumSignificantDigits')) {
   1143     defineWEProperty(resolved, 'minimumSignificantDigits', undefined);
   1144   }
   1145   if (internalOptions.hasOwnProperty('maximumSignificantDigits')) {
   1146     defineWEProperty(resolved, 'maximumSignificantDigits', undefined);
   1147   }
   1148   var formatter = %CreateNumberFormat(requestedLocale,
   1149                                       internalOptions,
   1150                                       resolved);
   1151 
   1152   // We can't get information about number or currency style from ICU, so we
   1153   // assume user request was fulfilled.
   1154   if (internalOptions.style === 'currency') {
   1155     ObjectDefineProperty(resolved, 'currencyDisplay', {value: currencyDisplay,
   1156                                                        writable: true});
   1157   }
   1158 
   1159   %MarkAsInitializedIntlObjectOfType(numberFormat, 'numberformat', formatter);
   1160   ObjectDefineProperty(numberFormat, 'resolved', {value: resolved});
   1161 
   1162   return numberFormat;
   1163 }
   1164 
   1165 
   1166 /**
   1167  * Constructs Intl.NumberFormat object given optional locales and options
   1168  * parameters.
   1169  *
   1170  * @constructor
   1171  */
   1172 %AddNamedProperty(Intl, 'NumberFormat', function() {
   1173     var locales = %_Arguments(0);
   1174     var options = %_Arguments(1);
   1175 
   1176     if (!this || this === Intl) {
   1177       // Constructor is called as a function.
   1178       return new Intl.NumberFormat(locales, options);
   1179     }
   1180 
   1181     return initializeNumberFormat(ToObject(this), locales, options);
   1182   },
   1183   DONT_ENUM
   1184 );
   1185 
   1186 
   1187 /**
   1188  * NumberFormat resolvedOptions method.
   1189  */
   1190 %AddNamedProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() {
   1191     if (%_IsConstructCall()) {
   1192       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1193     }
   1194 
   1195     if (!%IsInitializedIntlObjectOfType(this, 'numberformat')) {
   1196       throw new $TypeError('resolvedOptions method called on a non-object' +
   1197           ' or on a object that is not Intl.NumberFormat.');
   1198     }
   1199 
   1200     var format = this;
   1201     var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
   1202                                        format.resolved.locale);
   1203 
   1204     var result = {
   1205       locale: locale,
   1206       numberingSystem: format.resolved.numberingSystem,
   1207       style: format.resolved.style,
   1208       useGrouping: format.resolved.useGrouping,
   1209       minimumIntegerDigits: format.resolved.minimumIntegerDigits,
   1210       minimumFractionDigits: format.resolved.minimumFractionDigits,
   1211       maximumFractionDigits: format.resolved.maximumFractionDigits,
   1212     };
   1213 
   1214     if (result.style === 'currency') {
   1215       defineWECProperty(result, 'currency', format.resolved.currency);
   1216       defineWECProperty(result, 'currencyDisplay',
   1217                         format.resolved.currencyDisplay);
   1218     }
   1219 
   1220     if (format.resolved.hasOwnProperty('minimumSignificantDigits')) {
   1221       defineWECProperty(result, 'minimumSignificantDigits',
   1222                         format.resolved.minimumSignificantDigits);
   1223     }
   1224 
   1225     if (format.resolved.hasOwnProperty('maximumSignificantDigits')) {
   1226       defineWECProperty(result, 'maximumSignificantDigits',
   1227                         format.resolved.maximumSignificantDigits);
   1228     }
   1229 
   1230     return result;
   1231   },
   1232   DONT_ENUM
   1233 );
   1234 %FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions,
   1235                  'resolvedOptions');
   1236 %FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions);
   1237 %SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions);
   1238 
   1239 
   1240 /**
   1241  * Returns the subset of the given locale list for which this locale list
   1242  * has a matching (possibly fallback) locale. Locales appear in the same
   1243  * order in the returned list as in the input list.
   1244  * Options are optional parameter.
   1245  */
   1246 %AddNamedProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) {
   1247     if (%_IsConstructCall()) {
   1248       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1249     }
   1250 
   1251     return supportedLocalesOf('numberformat', locales, %_Arguments(1));
   1252   },
   1253   DONT_ENUM
   1254 );
   1255 %FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf');
   1256 %FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf);
   1257 %SetNativeFlag(Intl.NumberFormat.supportedLocalesOf);
   1258 
   1259 
   1260 /**
   1261  * Returns a String value representing the result of calling ToNumber(value)
   1262  * according to the effective locale and the formatting options of this
   1263  * NumberFormat.
   1264  */
   1265 function formatNumber(formatter, value) {
   1266   // Spec treats -0 and +0 as 0.
   1267   var number = $Number(value) + 0;
   1268 
   1269   return %InternalNumberFormat(%GetImplFromInitializedIntlObject(formatter),
   1270                                number);
   1271 }
   1272 
   1273 
   1274 /**
   1275  * Returns a Number that represents string value that was passed in.
   1276  */
   1277 function parseNumber(formatter, value) {
   1278   return %InternalNumberParse(%GetImplFromInitializedIntlObject(formatter),
   1279                               $String(value));
   1280 }
   1281 
   1282 
   1283 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1);
   1284 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1);
   1285 
   1286 /**
   1287  * Returns a string that matches LDML representation of the options object.
   1288  */
   1289 function toLDMLString(options) {
   1290   var getOption = getGetOption(options, 'dateformat');
   1291 
   1292   var ldmlString = '';
   1293 
   1294   var option = getOption('weekday', 'string', ['narrow', 'short', 'long']);
   1295   ldmlString += appendToLDMLString(
   1296       option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'});
   1297 
   1298   option = getOption('era', 'string', ['narrow', 'short', 'long']);
   1299   ldmlString += appendToLDMLString(
   1300       option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'});
   1301 
   1302   option = getOption('year', 'string', ['2-digit', 'numeric']);
   1303   ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'});
   1304 
   1305   option = getOption('month', 'string',
   1306                      ['2-digit', 'numeric', 'narrow', 'short', 'long']);
   1307   ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M',
   1308           'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'});
   1309 
   1310   option = getOption('day', 'string', ['2-digit', 'numeric']);
   1311   ldmlString += appendToLDMLString(
   1312       option, {'2-digit': 'dd', 'numeric': 'd'});
   1313 
   1314   var hr12 = getOption('hour12', 'boolean');
   1315   option = getOption('hour', 'string', ['2-digit', 'numeric']);
   1316   if (hr12 === undefined) {
   1317     ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'});
   1318   } else if (hr12 === true) {
   1319     ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'});
   1320   } else {
   1321     ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'});
   1322   }
   1323 
   1324   option = getOption('minute', 'string', ['2-digit', 'numeric']);
   1325   ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'});
   1326 
   1327   option = getOption('second', 'string', ['2-digit', 'numeric']);
   1328   ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'});
   1329 
   1330   option = getOption('timeZoneName', 'string', ['short', 'long']);
   1331   ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'});
   1332 
   1333   return ldmlString;
   1334 }
   1335 
   1336 
   1337 /**
   1338  * Returns either LDML equivalent of the current option or empty string.
   1339  */
   1340 function appendToLDMLString(option, pairs) {
   1341   if (option !== undefined) {
   1342     return pairs[option];
   1343   } else {
   1344     return '';
   1345   }
   1346 }
   1347 
   1348 
   1349 /**
   1350  * Returns object that matches LDML representation of the date.
   1351  */
   1352 function fromLDMLString(ldmlString) {
   1353   // First remove '' quoted text, so we lose 'Uhr' strings.
   1354   ldmlString = ldmlString.replace(GetQuotedStringRE(), '');
   1355 
   1356   var options = {};
   1357   var match = ldmlString.match(/E{3,5}/g);
   1358   options = appendToDateTimeObject(
   1359       options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'});
   1360 
   1361   match = ldmlString.match(/G{3,5}/g);
   1362   options = appendToDateTimeObject(
   1363       options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'});
   1364 
   1365   match = ldmlString.match(/y{1,2}/g);
   1366   options = appendToDateTimeObject(
   1367       options, 'year', match, {y: 'numeric', yy: '2-digit'});
   1368 
   1369   match = ldmlString.match(/M{1,5}/g);
   1370   options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit',
   1371       M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'});
   1372 
   1373   // Sometimes we get L instead of M for month - standalone name.
   1374   match = ldmlString.match(/L{1,5}/g);
   1375   options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit',
   1376       L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'});
   1377 
   1378   match = ldmlString.match(/d{1,2}/g);
   1379   options = appendToDateTimeObject(
   1380       options, 'day', match, {d: 'numeric', dd: '2-digit'});
   1381 
   1382   match = ldmlString.match(/h{1,2}/g);
   1383   if (match !== null) {
   1384     options['hour12'] = true;
   1385   }
   1386   options = appendToDateTimeObject(
   1387       options, 'hour', match, {h: 'numeric', hh: '2-digit'});
   1388 
   1389   match = ldmlString.match(/H{1,2}/g);
   1390   if (match !== null) {
   1391     options['hour12'] = false;
   1392   }
   1393   options = appendToDateTimeObject(
   1394       options, 'hour', match, {H: 'numeric', HH: '2-digit'});
   1395 
   1396   match = ldmlString.match(/m{1,2}/g);
   1397   options = appendToDateTimeObject(
   1398       options, 'minute', match, {m: 'numeric', mm: '2-digit'});
   1399 
   1400   match = ldmlString.match(/s{1,2}/g);
   1401   options = appendToDateTimeObject(
   1402       options, 'second', match, {s: 'numeric', ss: '2-digit'});
   1403 
   1404   match = ldmlString.match(/z|zzzz/g);
   1405   options = appendToDateTimeObject(
   1406       options, 'timeZoneName', match, {z: 'short', zzzz: 'long'});
   1407 
   1408   return options;
   1409 }
   1410 
   1411 
   1412 function appendToDateTimeObject(options, option, match, pairs) {
   1413   if (IS_NULL(match)) {
   1414     if (!options.hasOwnProperty(option)) {
   1415       defineWEProperty(options, option, undefined);
   1416     }
   1417     return options;
   1418   }
   1419 
   1420   var property = match[0];
   1421   defineWEProperty(options, option, pairs[property]);
   1422 
   1423   return options;
   1424 }
   1425 
   1426 
   1427 /**
   1428  * Returns options with at least default values in it.
   1429  */
   1430 function toDateTimeOptions(options, required, defaults) {
   1431   if (options === undefined) {
   1432     options = {};
   1433   } else {
   1434     options = TO_OBJECT_INLINE(options);
   1435   }
   1436 
   1437   var needsDefault = true;
   1438   if ((required === 'date' || required === 'any') &&
   1439       (options.weekday !== undefined || options.year !== undefined ||
   1440        options.month !== undefined || options.day !== undefined)) {
   1441     needsDefault = false;
   1442   }
   1443 
   1444   if ((required === 'time' || required === 'any') &&
   1445       (options.hour !== undefined || options.minute !== undefined ||
   1446        options.second !== undefined)) {
   1447     needsDefault = false;
   1448   }
   1449 
   1450   if (needsDefault && (defaults === 'date' || defaults === 'all')) {
   1451     ObjectDefineProperty(options, 'year', {value: 'numeric',
   1452                                            writable: true,
   1453                                            enumerable: true,
   1454                                            configurable: true});
   1455     ObjectDefineProperty(options, 'month', {value: 'numeric',
   1456                                             writable: true,
   1457                                             enumerable: true,
   1458                                             configurable: true});
   1459     ObjectDefineProperty(options, 'day', {value: 'numeric',
   1460                                           writable: true,
   1461                                           enumerable: true,
   1462                                           configurable: true});
   1463   }
   1464 
   1465   if (needsDefault && (defaults === 'time' || defaults === 'all')) {
   1466     ObjectDefineProperty(options, 'hour', {value: 'numeric',
   1467                                              writable: true,
   1468                                              enumerable: true,
   1469                                              configurable: true});
   1470     ObjectDefineProperty(options, 'minute', {value: 'numeric',
   1471                                                writable: true,
   1472                                                enumerable: true,
   1473                                                configurable: true});
   1474     ObjectDefineProperty(options, 'second', {value: 'numeric',
   1475                                                writable: true,
   1476                                                enumerable: true,
   1477                                                configurable: true});
   1478   }
   1479 
   1480   return options;
   1481 }
   1482 
   1483 
   1484 /**
   1485  * Initializes the given object so it's a valid DateTimeFormat instance.
   1486  * Useful for subclassing.
   1487  */
   1488 function initializeDateTimeFormat(dateFormat, locales, options) {
   1489 
   1490   if (%IsInitializedIntlObject(dateFormat)) {
   1491     throw new $TypeError('Trying to re-initialize DateTimeFormat object.');
   1492   }
   1493 
   1494   if (options === undefined) {
   1495     options = {};
   1496   }
   1497 
   1498   var locale = resolveLocale('dateformat', locales, options);
   1499 
   1500   options = toDateTimeOptions(options, 'any', 'date');
   1501 
   1502   var getOption = getGetOption(options, 'dateformat');
   1503 
   1504   // We implement only best fit algorithm, but still need to check
   1505   // if the formatMatcher values are in range.
   1506   var matcher = getOption('formatMatcher', 'string',
   1507                           ['basic', 'best fit'], 'best fit');
   1508 
   1509   // Build LDML string for the skeleton that we pass to the formatter.
   1510   var ldmlString = toLDMLString(options);
   1511 
   1512   // Filter out supported extension keys so we know what to put in resolved
   1513   // section later on.
   1514   // We need to pass calendar and number system to the method.
   1515   var tz = canonicalizeTimeZoneID(options.timeZone);
   1516 
   1517   // ICU prefers options to be passed using -u- extension key/values, so
   1518   // we need to build that.
   1519   var internalOptions = {};
   1520   var extensionMap = parseExtension(locale.extension);
   1521   var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP,
   1522                              getOption, internalOptions);
   1523 
   1524   var requestedLocale = locale.locale + extension;
   1525   var resolved = ObjectDefineProperties({}, {
   1526     calendar: {writable: true},
   1527     day: {writable: true},
   1528     era: {writable: true},
   1529     hour12: {writable: true},
   1530     hour: {writable: true},
   1531     locale: {writable: true},
   1532     minute: {writable: true},
   1533     month: {writable: true},
   1534     numberingSystem: {writable: true},
   1535     pattern: {writable: true},
   1536     requestedLocale: {value: requestedLocale, writable: true},
   1537     second: {writable: true},
   1538     timeZone: {writable: true},
   1539     timeZoneName: {writable: true},
   1540     tz: {value: tz, writable: true},
   1541     weekday: {writable: true},
   1542     year: {writable: true}
   1543   });
   1544 
   1545   var formatter = %CreateDateTimeFormat(
   1546     requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved);
   1547 
   1548   if (tz !== undefined && tz !== resolved.timeZone) {
   1549     throw new $RangeError('Unsupported time zone specified ' + tz);
   1550   }
   1551 
   1552   %MarkAsInitializedIntlObjectOfType(dateFormat, 'dateformat', formatter);
   1553   ObjectDefineProperty(dateFormat, 'resolved', {value: resolved});
   1554 
   1555   return dateFormat;
   1556 }
   1557 
   1558 
   1559 /**
   1560  * Constructs Intl.DateTimeFormat object given optional locales and options
   1561  * parameters.
   1562  *
   1563  * @constructor
   1564  */
   1565 %AddNamedProperty(Intl, 'DateTimeFormat', function() {
   1566     var locales = %_Arguments(0);
   1567     var options = %_Arguments(1);
   1568 
   1569     if (!this || this === Intl) {
   1570       // Constructor is called as a function.
   1571       return new Intl.DateTimeFormat(locales, options);
   1572     }
   1573 
   1574     return initializeDateTimeFormat(ToObject(this), locales, options);
   1575   },
   1576   DONT_ENUM
   1577 );
   1578 
   1579 
   1580 /**
   1581  * DateTimeFormat resolvedOptions method.
   1582  */
   1583 %AddNamedProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
   1584     if (%_IsConstructCall()) {
   1585       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1586     }
   1587 
   1588     if (!%IsInitializedIntlObjectOfType(this, 'dateformat')) {
   1589       throw new $TypeError('resolvedOptions method called on a non-object or ' +
   1590           'on a object that is not Intl.DateTimeFormat.');
   1591     }
   1592 
   1593     var format = this;
   1594     var fromPattern = fromLDMLString(format.resolved.pattern);
   1595     var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar];
   1596     if (userCalendar === undefined) {
   1597       // Use ICU name if we don't have a match. It shouldn't happen, but
   1598       // it would be too strict to throw for this.
   1599       userCalendar = format.resolved.calendar;
   1600     }
   1601 
   1602     var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
   1603                                        format.resolved.locale);
   1604 
   1605     var result = {
   1606       locale: locale,
   1607       numberingSystem: format.resolved.numberingSystem,
   1608       calendar: userCalendar,
   1609       timeZone: format.resolved.timeZone
   1610     };
   1611 
   1612     addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName);
   1613     addWECPropertyIfDefined(result, 'era', fromPattern.era);
   1614     addWECPropertyIfDefined(result, 'year', fromPattern.year);
   1615     addWECPropertyIfDefined(result, 'month', fromPattern.month);
   1616     addWECPropertyIfDefined(result, 'day', fromPattern.day);
   1617     addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday);
   1618     addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12);
   1619     addWECPropertyIfDefined(result, 'hour', fromPattern.hour);
   1620     addWECPropertyIfDefined(result, 'minute', fromPattern.minute);
   1621     addWECPropertyIfDefined(result, 'second', fromPattern.second);
   1622 
   1623     return result;
   1624   },
   1625   DONT_ENUM
   1626 );
   1627 %FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions,
   1628                  'resolvedOptions');
   1629 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions);
   1630 %SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions);
   1631 
   1632 
   1633 /**
   1634  * Returns the subset of the given locale list for which this locale list
   1635  * has a matching (possibly fallback) locale. Locales appear in the same
   1636  * order in the returned list as in the input list.
   1637  * Options are optional parameter.
   1638  */
   1639 %AddNamedProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) {
   1640     if (%_IsConstructCall()) {
   1641       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1642     }
   1643 
   1644     return supportedLocalesOf('dateformat', locales, %_Arguments(1));
   1645   },
   1646   DONT_ENUM
   1647 );
   1648 %FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf');
   1649 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf);
   1650 %SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf);
   1651 
   1652 
   1653 /**
   1654  * Returns a String value representing the result of calling ToNumber(date)
   1655  * according to the effective locale and the formatting options of this
   1656  * DateTimeFormat.
   1657  */
   1658 function formatDate(formatter, dateValue) {
   1659   var dateMs;
   1660   if (dateValue === undefined) {
   1661     dateMs = $Date.now();
   1662   } else {
   1663     dateMs = $Number(dateValue);
   1664   }
   1665 
   1666   if (!$isFinite(dateMs)) {
   1667     throw new $RangeError('Provided date is not in valid range.');
   1668   }
   1669 
   1670   return %InternalDateFormat(%GetImplFromInitializedIntlObject(formatter),
   1671                              new $Date(dateMs));
   1672 }
   1673 
   1674 
   1675 /**
   1676  * Returns a Date object representing the result of calling ToString(value)
   1677  * according to the effective locale and the formatting options of this
   1678  * DateTimeFormat.
   1679  * Returns undefined if date string cannot be parsed.
   1680  */
   1681 function parseDate(formatter, value) {
   1682   return %InternalDateParse(%GetImplFromInitializedIntlObject(formatter),
   1683                             $String(value));
   1684 }
   1685 
   1686 
   1687 // 0 because date is optional argument.
   1688 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0);
   1689 addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1);
   1690 
   1691 
   1692 /**
   1693  * Returns canonical Area/Location name, or throws an exception if the zone
   1694  * name is invalid IANA name.
   1695  */
   1696 function canonicalizeTimeZoneID(tzID) {
   1697   // Skip undefined zones.
   1698   if (tzID === undefined) {
   1699     return tzID;
   1700   }
   1701 
   1702   // Special case handling (UTC, GMT).
   1703   var upperID = tzID.toUpperCase();
   1704   if (upperID === 'UTC' || upperID === 'GMT' ||
   1705       upperID === 'ETC/UTC' || upperID === 'ETC/GMT') {
   1706     return 'UTC';
   1707   }
   1708 
   1709   // We expect only _ and / beside ASCII letters.
   1710   // All inputs should conform to Area/Location from now on.
   1711   var match = GetTimezoneNameCheckRE().exec(tzID);
   1712   if (IS_NULL(match)) {
   1713     throw new $RangeError('Expected Area/Location for time zone, got ' + tzID);
   1714   }
   1715 
   1716   var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]);
   1717   var i = 3;
   1718   while (match[i] !== undefined && i < match.length) {
   1719     result = result + '_' + toTitleCaseWord(match[i]);
   1720     i++;
   1721   }
   1722 
   1723   return result;
   1724 }
   1725 
   1726 /**
   1727  * Initializes the given object so it's a valid BreakIterator instance.
   1728  * Useful for subclassing.
   1729  */
   1730 function initializeBreakIterator(iterator, locales, options) {
   1731   if (%IsInitializedIntlObject(iterator)) {
   1732     throw new $TypeError('Trying to re-initialize v8BreakIterator object.');
   1733   }
   1734 
   1735   if (options === undefined) {
   1736     options = {};
   1737   }
   1738 
   1739   var getOption = getGetOption(options, 'breakiterator');
   1740 
   1741   var internalOptions = {};
   1742 
   1743   defineWEProperty(internalOptions, 'type', getOption(
   1744     'type', 'string', ['character', 'word', 'sentence', 'line'], 'word'));
   1745 
   1746   var locale = resolveLocale('breakiterator', locales, options);
   1747   var resolved = ObjectDefineProperties({}, {
   1748     requestedLocale: {value: locale.locale, writable: true},
   1749     type: {value: internalOptions.type, writable: true},
   1750     locale: {writable: true}
   1751   });
   1752 
   1753   var internalIterator = %CreateBreakIterator(locale.locale,
   1754                                               internalOptions,
   1755                                               resolved);
   1756 
   1757   %MarkAsInitializedIntlObjectOfType(iterator, 'breakiterator',
   1758                                      internalIterator);
   1759   ObjectDefineProperty(iterator, 'resolved', {value: resolved});
   1760 
   1761   return iterator;
   1762 }
   1763 
   1764 
   1765 /**
   1766  * Constructs Intl.v8BreakIterator object given optional locales and options
   1767  * parameters.
   1768  *
   1769  * @constructor
   1770  */
   1771 %AddNamedProperty(Intl, 'v8BreakIterator', function() {
   1772     var locales = %_Arguments(0);
   1773     var options = %_Arguments(1);
   1774 
   1775     if (!this || this === Intl) {
   1776       // Constructor is called as a function.
   1777       return new Intl.v8BreakIterator(locales, options);
   1778     }
   1779 
   1780     return initializeBreakIterator(ToObject(this), locales, options);
   1781   },
   1782   DONT_ENUM
   1783 );
   1784 
   1785 
   1786 /**
   1787  * BreakIterator resolvedOptions method.
   1788  */
   1789 %AddNamedProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions',
   1790   function() {
   1791     if (%_IsConstructCall()) {
   1792       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1793     }
   1794 
   1795     if (!%IsInitializedIntlObjectOfType(this, 'breakiterator')) {
   1796       throw new $TypeError('resolvedOptions method called on a non-object or ' +
   1797           'on a object that is not Intl.v8BreakIterator.');
   1798     }
   1799 
   1800     var segmenter = this;
   1801     var locale = getOptimalLanguageTag(segmenter.resolved.requestedLocale,
   1802                                        segmenter.resolved.locale);
   1803 
   1804     return {
   1805       locale: locale,
   1806       type: segmenter.resolved.type
   1807     };
   1808   },
   1809   DONT_ENUM
   1810 );
   1811 %FunctionSetName(Intl.v8BreakIterator.prototype.resolvedOptions,
   1812                  'resolvedOptions');
   1813 %FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions);
   1814 %SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions);
   1815 
   1816 
   1817 /**
   1818  * Returns the subset of the given locale list for which this locale list
   1819  * has a matching (possibly fallback) locale. Locales appear in the same
   1820  * order in the returned list as in the input list.
   1821  * Options are optional parameter.
   1822  */
   1823 %AddNamedProperty(Intl.v8BreakIterator, 'supportedLocalesOf',
   1824   function(locales) {
   1825     if (%_IsConstructCall()) {
   1826       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1827     }
   1828 
   1829     return supportedLocalesOf('breakiterator', locales, %_Arguments(1));
   1830   },
   1831   DONT_ENUM
   1832 );
   1833 %FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf');
   1834 %FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf);
   1835 %SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf);
   1836 
   1837 
   1838 /**
   1839  * Adopts text to segment using the iterator. Old text, if present,
   1840  * gets discarded.
   1841  */
   1842 function adoptText(iterator, text) {
   1843   %BreakIteratorAdoptText(%GetImplFromInitializedIntlObject(iterator),
   1844                           $String(text));
   1845 }
   1846 
   1847 
   1848 /**
   1849  * Returns index of the first break in the string and moves current pointer.
   1850  */
   1851 function first(iterator) {
   1852   return %BreakIteratorFirst(%GetImplFromInitializedIntlObject(iterator));
   1853 }
   1854 
   1855 
   1856 /**
   1857  * Returns the index of the next break and moves the pointer.
   1858  */
   1859 function next(iterator) {
   1860   return %BreakIteratorNext(%GetImplFromInitializedIntlObject(iterator));
   1861 }
   1862 
   1863 
   1864 /**
   1865  * Returns index of the current break.
   1866  */
   1867 function current(iterator) {
   1868   return %BreakIteratorCurrent(%GetImplFromInitializedIntlObject(iterator));
   1869 }
   1870 
   1871 
   1872 /**
   1873  * Returns type of the current break.
   1874  */
   1875 function breakType(iterator) {
   1876   return %BreakIteratorBreakType(%GetImplFromInitializedIntlObject(iterator));
   1877 }
   1878 
   1879 
   1880 addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1);
   1881 addBoundMethod(Intl.v8BreakIterator, 'first', first, 0);
   1882 addBoundMethod(Intl.v8BreakIterator, 'next', next, 0);
   1883 addBoundMethod(Intl.v8BreakIterator, 'current', current, 0);
   1884 addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0);
   1885 
   1886 // Save references to Intl objects and methods we use, for added security.
   1887 var savedObjects = {
   1888   'collator': Intl.Collator,
   1889   'numberformat': Intl.NumberFormat,
   1890   'dateformatall': Intl.DateTimeFormat,
   1891   'dateformatdate': Intl.DateTimeFormat,
   1892   'dateformattime': Intl.DateTimeFormat
   1893 };
   1894 
   1895 
   1896 // Default (created with undefined locales and options parameters) collator,
   1897 // number and date format instances. They'll be created as needed.
   1898 var defaultObjects = {
   1899   'collator': undefined,
   1900   'numberformat': undefined,
   1901   'dateformatall': undefined,
   1902   'dateformatdate': undefined,
   1903   'dateformattime': undefined,
   1904 };
   1905 
   1906 
   1907 /**
   1908  * Returns cached or newly created instance of a given service.
   1909  * We cache only default instances (where no locales or options are provided).
   1910  */
   1911 function cachedOrNewService(service, locales, options, defaults) {
   1912   var useOptions = (defaults === undefined) ? options : defaults;
   1913   if (locales === undefined && options === undefined) {
   1914     if (defaultObjects[service] === undefined) {
   1915       defaultObjects[service] = new savedObjects[service](locales, useOptions);
   1916     }
   1917     return defaultObjects[service];
   1918   }
   1919   return new savedObjects[service](locales, useOptions);
   1920 }
   1921 
   1922 
   1923 /**
   1924  * Compares this and that, and returns less than 0, 0 or greater than 0 value.
   1925  * Overrides the built-in method.
   1926  */
   1927 ObjectDefineProperty($String.prototype, 'localeCompare', {
   1928   value: function(that) {
   1929     if (%_IsConstructCall()) {
   1930       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1931     }
   1932 
   1933     if (IS_NULL_OR_UNDEFINED(this)) {
   1934       throw new $TypeError('Method invoked on undefined or null value.');
   1935     }
   1936 
   1937     var locales = %_Arguments(1);
   1938     var options = %_Arguments(2);
   1939     var collator = cachedOrNewService('collator', locales, options);
   1940     return compare(collator, this, that);
   1941   },
   1942   writable: true,
   1943   configurable: true,
   1944   enumerable: false
   1945 });
   1946 %FunctionSetName($String.prototype.localeCompare, 'localeCompare');
   1947 %FunctionRemovePrototype($String.prototype.localeCompare);
   1948 %SetNativeFlag($String.prototype.localeCompare);
   1949 
   1950 
   1951 /**
   1952  * Unicode normalization. This method is called with one argument that
   1953  * specifies the normalization form.
   1954  * If none is specified, "NFC" is assumed.
   1955  * If the form is not one of "NFC", "NFD", "NFKC", or "NFKD", then throw
   1956  * a RangeError Exception.
   1957  */
   1958 ObjectDefineProperty($String.prototype, 'normalize', {
   1959   value: function(that) {
   1960     if (%_IsConstructCall()) {
   1961       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1962     }
   1963 
   1964     CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
   1965 
   1966     var form = $String(%_Arguments(0) || 'NFC');
   1967 
   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(', ') + '.');
   1972     }
   1973 
   1974     return %StringNormalize(this, normalizationForm);
   1975   },
   1976   writable: true,
   1977   configurable: true,
   1978   enumerable: false
   1979 });
   1980 %FunctionSetName($String.prototype.normalize, 'normalize');
   1981 %FunctionRemovePrototype($String.prototype.normalize);
   1982 %SetNativeFlag($String.prototype.normalize);
   1983 
   1984 
   1985 /**
   1986  * Formats a Number object (this) using locale and options values.
   1987  * If locale or options are omitted, defaults are used.
   1988  */
   1989 ObjectDefineProperty($Number.prototype, 'toLocaleString', {
   1990   value: function() {
   1991     if (%_IsConstructCall()) {
   1992       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1993     }
   1994 
   1995     if (!(this instanceof $Number) && typeof(this) !== 'number') {
   1996       throw new $TypeError('Method invoked on an object that is not Number.');
   1997     }
   1998 
   1999     var locales = %_Arguments(0);
   2000     var options = %_Arguments(1);
   2001     var numberFormat = cachedOrNewService('numberformat', locales, options);
   2002     return formatNumber(numberFormat, this);
   2003   },
   2004   writable: true,
   2005   configurable: true,
   2006   enumerable: false
   2007 });
   2008 %FunctionSetName($Number.prototype.toLocaleString, 'toLocaleString');
   2009 %FunctionRemovePrototype($Number.prototype.toLocaleString);
   2010 %SetNativeFlag($Number.prototype.toLocaleString);
   2011 
   2012 
   2013 /**
   2014  * Returns actual formatted date or fails if date parameter is invalid.
   2015  */
   2016 function toLocaleDateTime(date, locales, options, required, defaults, service) {
   2017   if (!(date instanceof $Date)) {
   2018     throw new $TypeError('Method invoked on an object that is not Date.');
   2019   }
   2020 
   2021   if ($isNaN(date)) {
   2022     return 'Invalid Date';
   2023   }
   2024 
   2025   var internalOptions = toDateTimeOptions(options, required, defaults);
   2026 
   2027   var dateFormat =
   2028       cachedOrNewService(service, locales, options, internalOptions);
   2029 
   2030   return formatDate(dateFormat, date);
   2031 }
   2032 
   2033 
   2034 /**
   2035  * Formats a Date object (this) using locale and options values.
   2036  * If locale or options are omitted, defaults are used - both date and time are
   2037  * present in the output.
   2038  */
   2039 ObjectDefineProperty($Date.prototype, 'toLocaleString', {
   2040   value: function() {
   2041     if (%_IsConstructCall()) {
   2042       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   2043     }
   2044 
   2045     var locales = %_Arguments(0);
   2046     var options = %_Arguments(1);
   2047     return toLocaleDateTime(
   2048         this, locales, options, 'any', 'all', 'dateformatall');
   2049   },
   2050   writable: true,
   2051   configurable: true,
   2052   enumerable: false
   2053 });
   2054 %FunctionSetName($Date.prototype.toLocaleString, 'toLocaleString');
   2055 %FunctionRemovePrototype($Date.prototype.toLocaleString);
   2056 %SetNativeFlag($Date.prototype.toLocaleString);
   2057 
   2058 
   2059 /**
   2060  * Formats a Date object (this) using locale and options values.
   2061  * If locale or options are omitted, defaults are used - only date is present
   2062  * in the output.
   2063  */
   2064 ObjectDefineProperty($Date.prototype, 'toLocaleDateString', {
   2065   value: function() {
   2066     if (%_IsConstructCall()) {
   2067       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   2068     }
   2069 
   2070     var locales = %_Arguments(0);
   2071     var options = %_Arguments(1);
   2072     return toLocaleDateTime(
   2073         this, locales, options, 'date', 'date', 'dateformatdate');
   2074   },
   2075   writable: true,
   2076   configurable: true,
   2077   enumerable: false
   2078 });
   2079 %FunctionSetName($Date.prototype.toLocaleDateString, 'toLocaleDateString');
   2080 %FunctionRemovePrototype($Date.prototype.toLocaleDateString);
   2081 %SetNativeFlag($Date.prototype.toLocaleDateString);
   2082 
   2083 
   2084 /**
   2085  * Formats a Date object (this) using locale and options values.
   2086  * If locale or options are omitted, defaults are used - only time is present
   2087  * in the output.
   2088  */
   2089 ObjectDefineProperty($Date.prototype, 'toLocaleTimeString', {
   2090   value: function() {
   2091     if (%_IsConstructCall()) {
   2092       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   2093     }
   2094 
   2095     var locales = %_Arguments(0);
   2096     var options = %_Arguments(1);
   2097     return toLocaleDateTime(
   2098         this, locales, options, 'time', 'time', 'dateformattime');
   2099   },
   2100   writable: true,
   2101   configurable: true,
   2102   enumerable: false
   2103 });
   2104 %FunctionSetName($Date.prototype.toLocaleTimeString, 'toLocaleTimeString');
   2105 %FunctionRemovePrototype($Date.prototype.toLocaleTimeString);
   2106 %SetNativeFlag($Date.prototype.toLocaleTimeString);
   2107 
   2108 return Intl;
   2109 }())});
   2110