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