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