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