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