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