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