UNPKG

45 kBJavaScriptView Raw
1// 12.1 The Intl.DateTimeFormat constructor
2// ==================================
3
4import {
5 toLatinUpperCase,
6} from './6.locales-currencies-tz.js';
7
8import {
9 Intl,
10} from "./8.intl.js";
11
12import {
13 CanonicalizeLocaleList,
14 ResolveLocale,
15 GetOption,
16 SupportedLocales,
17} from "./9.negotiation.js";
18
19import {
20 FormatNumber,
21} from "./11.numberformat.js";
22
23import {
24 createDateTimeFormats,
25} from "./cldr";
26
27import {
28 internals,
29 es3,
30 fnBind,
31 defineProperty,
32 toObject,
33 getInternalProperties,
34 createRegExpRestore,
35 secret,
36 Record,
37 List,
38 hop,
39 objCreate,
40 arrPush,
41 arrIndexOf,
42} from './util.js';
43
44// An object map of date component keys, saves using a regex later
45const dateWidths = objCreate(null, { narrow:{}, short:{}, long:{} });
46
47/**
48 * Returns a string for a date component, resolved using multiple inheritance as specified
49 * as specified in the Unicode Technical Standard 35.
50 */
51function resolveDateString(data, ca, component, width, key) {
52 // From http://www.unicode.org/reports/tr35/tr35.html#Multiple_Inheritance:
53 // 'In clearly specified instances, resources may inherit from within the same locale.
54 // For example, ... the Buddhist calendar inherits from the Gregorian calendar.'
55 let obj = data[ca] && data[ca][component]
56 ? data[ca][component]
57 : data.gregory[component],
58
59 // "sideways" inheritance resolves strings when a key doesn't exist
60 alts = {
61 narrow: ['short', 'long'],
62 short: ['long', 'narrow'],
63 long: ['short', 'narrow'],
64 },
65
66 //
67 resolved = hop.call(obj, width)
68 ? obj[width]
69 : hop.call(obj, alts[width][0])
70 ? obj[alts[width][0]]
71 : obj[alts[width][1]];
72
73 // `key` wouldn't be specified for components 'dayPeriods'
74 return key !== null ? resolved[key] : resolved;
75}
76
77// Define the DateTimeFormat constructor internally so it cannot be tainted
78export function DateTimeFormatConstructor () {
79 let locales = arguments[0];
80 let options = arguments[1];
81
82 if (!this || this === Intl) {
83 return new Intl.DateTimeFormat(locales, options);
84 }
85 return InitializeDateTimeFormat(toObject(this), locales, options);
86}
87
88defineProperty(Intl, 'DateTimeFormat', {
89 configurable: true,
90 writable: true,
91 value: DateTimeFormatConstructor,
92});
93
94// Must explicitly set prototypes as unwritable
95defineProperty(DateTimeFormatConstructor, 'prototype', {
96 writable: false,
97});
98
99/**
100 * The abstract operation InitializeDateTimeFormat accepts the arguments dateTimeFormat
101 * (which must be an object), locales, and options. It initializes dateTimeFormat as a
102 * DateTimeFormat object.
103 */
104export function/* 12.1.1.1 */InitializeDateTimeFormat (dateTimeFormat, locales, options) {
105 // This will be a internal properties object if we're not already initialized
106 let internal = getInternalProperties(dateTimeFormat);
107
108 // Create an object whose props can be used to restore the values of RegExp props
109 let regexpState = createRegExpRestore();
110
111 // 1. If dateTimeFormat has an [[initializedIntlObject]] internal property with
112 // value true, throw a TypeError exception.
113 if (internal['[[initializedIntlObject]]'] === true)
114 throw new TypeError('`this` object has already been initialized as an Intl object');
115
116 // Need this to access the `internal` object
117 defineProperty(dateTimeFormat, '__getInternalProperties', {
118 value: function () {
119 // NOTE: Non-standard, for internal use only
120 if (arguments[0] === secret)
121 return internal;
122 },
123 });
124
125 // 2. Set the [[initializedIntlObject]] internal property of numberFormat to true.
126 internal['[[initializedIntlObject]]'] = true;
127
128 // 3. Let requestedLocales be the result of calling the CanonicalizeLocaleList
129 // abstract operation (defined in 9.2.1) with argument locales.
130 let requestedLocales = CanonicalizeLocaleList(locales);
131
132 // 4. Let options be the result of calling the ToDateTimeOptions abstract
133 // operation (defined below) with arguments options, "any", and "date".
134 options = ToDateTimeOptions(options, 'any', 'date');
135
136 // 5. Let opt be a new Record.
137 let opt = new Record();
138
139 // 6. Let matcher be the result of calling the GetOption abstract operation
140 // (defined in 9.2.9) with arguments options, "localeMatcher", "string", a List
141 // containing the two String values "lookup" and "best fit", and "best fit".
142 let matcher = GetOption(options, 'localeMatcher', 'string', new List('lookup', 'best fit'), 'best fit');
143
144 // 7. Set opt.[[localeMatcher]] to matcher.
145 opt['[[localeMatcher]]'] = matcher;
146
147 // 8. Let DateTimeFormat be the standard built-in object that is the initial
148 // value of Intl.DateTimeFormat.
149 let DateTimeFormat = internals.DateTimeFormat; // This is what we *really* need
150
151 // 9. Let localeData be the value of the [[localeData]] internal property of
152 // DateTimeFormat.
153 let localeData = DateTimeFormat['[[localeData]]'];
154
155 // 10. Let r be the result of calling the ResolveLocale abstract operation
156 // (defined in 9.2.5) with the [[availableLocales]] internal property of
157 // DateTimeFormat, requestedLocales, opt, the [[relevantExtensionKeys]]
158 // internal property of DateTimeFormat, and localeData.
159 let r = ResolveLocale(DateTimeFormat['[[availableLocales]]'], requestedLocales,
160 opt, DateTimeFormat['[[relevantExtensionKeys]]'], localeData);
161
162 // 11. Set the [[locale]] internal property of dateTimeFormat to the value of
163 // r.[[locale]].
164 internal['[[locale]]'] = r['[[locale]]'];
165
166 // 12. Set the [[calendar]] internal property of dateTimeFormat to the value of
167 // r.[[ca]].
168 internal['[[calendar]]'] = r['[[ca]]'];
169
170 // 13. Set the [[numberingSystem]] internal property of dateTimeFormat to the value of
171 // r.[[nu]].
172 internal['[[numberingSystem]]'] = r['[[nu]]'];
173
174 // The specification doesn't tell us to do this, but it's helpful later on
175 internal['[[dataLocale]]'] = r['[[dataLocale]]'];
176
177 // 14. Let dataLocale be the value of r.[[dataLocale]].
178 let dataLocale = r['[[dataLocale]]'];
179
180 // 15. Let tz be the result of calling the [[Get]] internal method of options with
181 // argument "timeZone".
182 let tz = options.timeZone;
183
184 // 16. If tz is not undefined, then
185 if (tz !== undefined) {
186 // a. Let tz be ToString(tz).
187 // b. Convert tz to upper case as described in 6.1.
188 // NOTE: If an implementation accepts additional time zone values, as permitted
189 // under certain conditions by the Conformance clause, different casing
190 // rules apply.
191 tz = toLatinUpperCase(tz);
192
193 // c. If tz is not "UTC", then throw a RangeError exception.
194 // ###TODO: accept more time zones###
195 if (tz !== 'UTC')
196 throw new RangeError('timeZone is not supported.');
197 }
198
199 // 17. Set the [[timeZone]] internal property of dateTimeFormat to tz.
200 internal['[[timeZone]]'] = tz;
201
202 // 18. Let opt be a new Record.
203 opt = new Record();
204
205 // 19. For each row of Table 3, except the header row, do:
206 for (let prop in dateTimeComponents) {
207 if (!hop.call(dateTimeComponents, prop))
208 continue;
209
210 // 20. Let prop be the name given in the Property column of the row.
211 // 21. Let value be the result of calling the GetOption abstract operation,
212 // passing as argument options, the name given in the Property column of the
213 // row, "string", a List containing the strings given in the Values column of
214 // the row, and undefined.
215 let value = GetOption(options, prop, 'string', dateTimeComponents[prop]);
216
217 // 22. Set opt.[[<prop>]] to value.
218 opt['[['+prop+']]'] = value;
219 }
220
221 // Assigned a value below
222 let bestFormat;
223
224 // 23. Let dataLocaleData be the result of calling the [[Get]] internal method of
225 // localeData with argument dataLocale.
226 let dataLocaleData = localeData[dataLocale];
227
228 // 24. Let formats be the result of calling the [[Get]] internal method of
229 // dataLocaleData with argument "formats".
230 // Note: we process the CLDR formats into the spec'd structure
231 let formats = ToDateTimeFormats(dataLocaleData.formats);
232
233 // 25. Let matcher be the result of calling the GetOption abstract operation with
234 // arguments options, "formatMatcher", "string", a List containing the two String
235 // values "basic" and "best fit", and "best fit".
236 matcher = GetOption(options, 'formatMatcher', 'string', new List('basic', 'best fit'), 'best fit');
237
238 // Optimization: caching the processed formats as a one time operation by
239 // replacing the initial structure from localeData
240 dataLocaleData.formats = formats;
241
242 // 26. If matcher is "basic", then
243 if (matcher === 'basic') {
244 // 27. Let bestFormat be the result of calling the BasicFormatMatcher abstract
245 // operation (defined below) with opt and formats.
246 bestFormat = BasicFormatMatcher(opt, formats);
247
248 // 28. Else
249 } else {
250 {
251 // diverging
252 let hr12 = GetOption(options, 'hour12', 'boolean'/*, undefined, undefined*/);
253 opt.hour12 = hr12 === undefined ? dataLocaleData.hour12 : hr12;
254 }
255 // 29. Let bestFormat be the result of calling the BestFitFormatMatcher
256 // abstract operation (defined below) with opt and formats.
257 bestFormat = BestFitFormatMatcher(opt, formats);
258 }
259
260 // 30. For each row in Table 3, except the header row, do
261 for (let prop in dateTimeComponents) {
262 if (!hop.call(dateTimeComponents, prop))
263 continue;
264
265 // a. Let prop be the name given in the Property column of the row.
266 // b. Let pDesc be the result of calling the [[GetOwnProperty]] internal method of
267 // bestFormat with argument prop.
268 // c. If pDesc is not undefined, then
269 if (hop.call(bestFormat, prop)) {
270 // i. Let p be the result of calling the [[Get]] internal method of bestFormat
271 // with argument prop.
272 let p = bestFormat[prop];
273 {
274 // diverging
275 p = bestFormat._ && hop.call(bestFormat._, prop) ? bestFormat._[prop] : p;
276 }
277
278 // ii. Set the [[<prop>]] internal property of dateTimeFormat to p.
279 internal['[['+prop+']]'] = p;
280 }
281 }
282
283 let pattern; // Assigned a value below
284
285 // 31. Let hr12 be the result of calling the GetOption abstract operation with
286 // arguments options, "hour12", "boolean", undefined, and undefined.
287 let hr12 = GetOption(options, 'hour12', 'boolean'/*, undefined, undefined*/);
288
289 // 32. If dateTimeFormat has an internal property [[hour]], then
290 if (internal['[[hour]]']) {
291 // a. If hr12 is undefined, then let hr12 be the result of calling the [[Get]]
292 // internal method of dataLocaleData with argument "hour12".
293 hr12 = hr12 === undefined ? dataLocaleData.hour12 : hr12;
294
295 // b. Set the [[hour12]] internal property of dateTimeFormat to hr12.
296 internal['[[hour12]]'] = hr12;
297
298 // c. If hr12 is true, then
299 if (hr12 === true) {
300 // i. Let hourNo0 be the result of calling the [[Get]] internal method of
301 // dataLocaleData with argument "hourNo0".
302 let hourNo0 = dataLocaleData.hourNo0;
303
304 // ii. Set the [[hourNo0]] internal property of dateTimeFormat to hourNo0.
305 internal['[[hourNo0]]'] = hourNo0;
306
307 // iii. Let pattern be the result of calling the [[Get]] internal method of
308 // bestFormat with argument "pattern12".
309 pattern = bestFormat.pattern12;
310 }
311
312 // d. Else
313 else
314 // i. Let pattern be the result of calling the [[Get]] internal method of
315 // bestFormat with argument "pattern".
316 pattern = bestFormat.pattern;
317 }
318
319 // 33. Else
320 else
321 // a. Let pattern be the result of calling the [[Get]] internal method of
322 // bestFormat with argument "pattern".
323 pattern = bestFormat.pattern;
324
325 // 34. Set the [[pattern]] internal property of dateTimeFormat to pattern.
326 internal['[[pattern]]'] = pattern;
327
328 // 35. Set the [[boundFormat]] internal property of dateTimeFormat to undefined.
329 internal['[[boundFormat]]'] = undefined;
330
331 // 36. Set the [[initializedDateTimeFormat]] internal property of dateTimeFormat to
332 // true.
333 internal['[[initializedDateTimeFormat]]'] = true;
334
335 // In ES3, we need to pre-bind the format() function
336 if (es3)
337 dateTimeFormat.format = GetFormatDateTime.call(dateTimeFormat);
338
339 // Restore the RegExp properties
340 regexpState.exp.test(regexpState.input);
341
342 // Return the newly initialised object
343 return dateTimeFormat;
344}
345
346/**
347 * Several DateTimeFormat algorithms use values from the following table, which provides
348 * property names and allowable values for the components of date and time formats:
349 */
350let dateTimeComponents = {
351 weekday: [ "narrow", "short", "long" ],
352 era: [ "narrow", "short", "long" ],
353 year: [ "2-digit", "numeric" ],
354 month: [ "2-digit", "numeric", "narrow", "short", "long" ],
355 day: [ "2-digit", "numeric" ],
356 hour: [ "2-digit", "numeric" ],
357 minute: [ "2-digit", "numeric" ],
358 second: [ "2-digit", "numeric" ],
359 timeZoneName: [ "short", "long" ],
360};
361
362/**
363 * When the ToDateTimeOptions abstract operation is called with arguments options,
364 * required, and defaults, the following steps are taken:
365 */
366function ToDateTimeFormats(formats) {
367 if (Object.prototype.toString.call(formats) === '[object Array]') {
368 return formats;
369 }
370 return createDateTimeFormats(formats);
371}
372
373/**
374 * When the ToDateTimeOptions abstract operation is called with arguments options,
375 * required, and defaults, the following steps are taken:
376 */
377export function ToDateTimeOptions (options, required, defaults) {
378 // 1. If options is undefined, then let options be null, else let options be
379 // ToObject(options).
380 if (options === undefined)
381 options = null;
382
383 else {
384 // (#12) options needs to be a Record, but it also needs to inherit properties
385 let opt2 = toObject(options);
386 options = new Record();
387
388 for (let k in opt2)
389 options[k] = opt2[k];
390 }
391
392 // 2. Let create be the standard built-in function object defined in ES5, 15.2.3.5.
393 let create = objCreate;
394
395 // 3. Let options be the result of calling the [[Call]] internal method of create with
396 // undefined as the this value and an argument list containing the single item
397 // options.
398 options = create(options);
399
400 // 4. Let needDefaults be true.
401 let needDefaults = true;
402
403 // 5. If required is "date" or "any", then
404 if (required === 'date' || required === 'any') {
405 // a. For each of the property names "weekday", "year", "month", "day":
406 // i. If the result of calling the [[Get]] internal method of options with the
407 // property name is not undefined, then let needDefaults be false.
408 if (options.weekday !== undefined || options.year !== undefined
409 || options.month !== undefined || options.day !== undefined)
410 needDefaults = false;
411 }
412
413 // 6. If required is "time" or "any", then
414 if (required === 'time' || required === 'any') {
415 // a. For each of the property names "hour", "minute", "second":
416 // i. If the result of calling the [[Get]] internal method of options with the
417 // property name is not undefined, then let needDefaults be false.
418 if (options.hour !== undefined || options.minute !== undefined || options.second !== undefined)
419 needDefaults = false;
420 }
421
422 // 7. If needDefaults is true and defaults is either "date" or "all", then
423 if (needDefaults && (defaults === 'date' || defaults === 'all'))
424 // a. For each of the property names "year", "month", "day":
425 // i. Call the [[DefineOwnProperty]] internal method of options with the
426 // property name, Property Descriptor {[[Value]]: "numeric", [[Writable]]:
427 // true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
428 options.year = options.month = options.day = 'numeric';
429
430 // 8. If needDefaults is true and defaults is either "time" or "all", then
431 if (needDefaults && (defaults === 'time' || defaults === 'all'))
432 // a. For each of the property names "hour", "minute", "second":
433 // i. Call the [[DefineOwnProperty]] internal method of options with the
434 // property name, Property Descriptor {[[Value]]: "numeric", [[Writable]]:
435 // true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
436 options.hour = options.minute = options.second = 'numeric';
437
438 // 9. Return options.
439 return options;
440}
441
442/**
443 * When the BasicFormatMatcher abstract operation is called with two arguments options and
444 * formats, the following steps are taken:
445 */
446function BasicFormatMatcher (options, formats) {
447 // 1. Let removalPenalty be 120.
448 let removalPenalty = 120;
449
450 // 2. Let additionPenalty be 20.
451 let additionPenalty = 20;
452
453 // 3. Let longLessPenalty be 8.
454 let longLessPenalty = 8;
455
456 // 4. Let longMorePenalty be 6.
457 let longMorePenalty = 6;
458
459 // 5. Let shortLessPenalty be 6.
460 let shortLessPenalty = 6;
461
462 // 6. Let shortMorePenalty be 3.
463 let shortMorePenalty = 3;
464
465 // 7. Let bestScore be -Infinity.
466 let bestScore = -Infinity;
467
468 // 8. Let bestFormat be undefined.
469 let bestFormat;
470
471 // 9. Let i be 0.
472 let i = 0;
473
474 // 10. Assert: formats is an Array object.
475
476 // 11. Let len be the result of calling the [[Get]] internal method of formats with argument "length".
477 let len = formats.length;
478
479 // 12. Repeat while i < len:
480 while (i < len) {
481 // a. Let format be the result of calling the [[Get]] internal method of formats with argument ToString(i).
482 let format = formats[i];
483
484 // b. Let score be 0.
485 let score = 0;
486
487 // c. For each property shown in Table 3:
488 for (let property in dateTimeComponents) {
489 if (!hop.call(dateTimeComponents, property))
490 continue;
491
492 // i. Let optionsProp be options.[[<property>]].
493 let optionsProp = options['[['+ property +']]'];
494
495 // ii. Let formatPropDesc be the result of calling the [[GetOwnProperty]] internal method of format
496 // with argument property.
497 // iii. If formatPropDesc is not undefined, then
498 // 1. Let formatProp be the result of calling the [[Get]] internal method of format with argument property.
499 let formatProp = hop.call(format, property) ? format[property] : undefined;
500
501 // iv. If optionsProp is undefined and formatProp is not undefined, then decrease score by
502 // additionPenalty.
503 if (optionsProp === undefined && formatProp !== undefined)
504 score -= additionPenalty;
505
506 // v. Else if optionsProp is not undefined and formatProp is undefined, then decrease score by
507 // removalPenalty.
508 else if (optionsProp !== undefined && formatProp === undefined)
509 score -= removalPenalty;
510
511 // vi. Else
512 else {
513 // 1. Let values be the array ["2-digit", "numeric", "narrow", "short",
514 // "long"].
515 let values = [ '2-digit', 'numeric', 'narrow', 'short', 'long' ];
516
517 // 2. Let optionsPropIndex be the index of optionsProp within values.
518 let optionsPropIndex = arrIndexOf.call(values, optionsProp);
519
520 // 3. Let formatPropIndex be the index of formatProp within values.
521 let formatPropIndex = arrIndexOf.call(values, formatProp);
522
523 // 4. Let delta be max(min(formatPropIndex - optionsPropIndex, 2), -2).
524 let delta = Math.max(Math.min(formatPropIndex - optionsPropIndex, 2), -2);
525
526 // 5. If delta = 2, decrease score by longMorePenalty.
527 if (delta === 2)
528 score -= longMorePenalty;
529
530 // 6. Else if delta = 1, decrease score by shortMorePenalty.
531 else if (delta === 1)
532 score -= shortMorePenalty;
533
534 // 7. Else if delta = -1, decrease score by shortLessPenalty.
535 else if (delta === -1)
536 score -= shortLessPenalty;
537
538 // 8. Else if delta = -2, decrease score by longLessPenalty.
539 else if (delta === -2)
540 score -= longLessPenalty;
541 }
542 }
543
544 // d. If score > bestScore, then
545 if (score > bestScore) {
546 // i. Let bestScore be score.
547 bestScore = score;
548
549 // ii. Let bestFormat be format.
550 bestFormat = format;
551 }
552
553 // e. Increase i by 1.
554 i++;
555 }
556
557 // 13. Return bestFormat.
558 return bestFormat;
559}
560
561/**
562 * When the BestFitFormatMatcher abstract operation is called with two arguments options
563 * and formats, it performs implementation dependent steps, which should return a set of
564 * component representations that a typical user of the selected locale would perceive as
565 * at least as good as the one returned by BasicFormatMatcher.
566 *
567 * This polyfill defines the algorithm to be the same as BasicFormatMatcher,
568 * with the addition of bonus points awarded where the requested format is of
569 * the same data type as the potentially matching format.
570 *
571 * This algo relies on the concept of closest distance matching described here:
572 * http://unicode.org/reports/tr35/tr35-dates.html#Matching_Skeletons
573 * Typically a “best match” is found using a closest distance match, such as:
574 *
575 * Symbols requesting a best choice for the locale are replaced.
576 * j → one of {H, k, h, K}; C → one of {a, b, B}
577 * -> Covered by cldr.js matching process
578 *
579 * For fields with symbols representing the same type (year, month, day, etc):
580 * Most symbols have a small distance from each other.
581 * M ≅ L; E ≅ c; a ≅ b ≅ B; H ≅ k ≅ h ≅ K; ...
582 * -> Covered by cldr.js matching process
583 *
584 * Width differences among fields, other than those marking text vs numeric, are given small distance from each other.
585 * MMM ≅ MMMM
586 * MM ≅ M
587 * Numeric and text fields are given a larger distance from each other.
588 * MMM ≈ MM
589 * Symbols representing substantial differences (week of year vs week of month) are given much larger a distances from each other.
590 * d ≋ D; ...
591 * Missing or extra fields cause a match to fail. (But see Missing Skeleton Fields).
592 *
593 *
594 * For example,
595 *
596 * { month: 'numeric', day: 'numeric' }
597 *
598 * should match
599 *
600 * { month: '2-digit', day: '2-digit' }
601 *
602 * rather than
603 *
604 * { month: 'short', day: 'numeric' }
605 *
606 * This makes sense because a user requesting a formatted date with numeric parts would
607 * not expect to see the returned format containing narrow, short or long part names
608 */
609function BestFitFormatMatcher (options, formats) {
610
611 // 1. Let removalPenalty be 120.
612 let removalPenalty = 120;
613
614 // 2. Let additionPenalty be 20.
615 let additionPenalty = 20;
616
617 // 3. Let longLessPenalty be 8.
618 let longLessPenalty = 8;
619
620 // 4. Let longMorePenalty be 6.
621 let longMorePenalty = 6;
622
623 // 5. Let shortLessPenalty be 6.
624 let shortLessPenalty = 6;
625
626 // 6. Let shortMorePenalty be 3.
627 let shortMorePenalty = 3;
628
629 let hour12Penalty = 1;
630
631 // 7. Let bestScore be -Infinity.
632 let bestScore = -Infinity;
633
634 // 8. Let bestFormat be undefined.
635 let bestFormat;
636
637 // 9. Let i be 0.
638 let i = 0;
639
640 // 10. Assert: formats is an Array object.
641
642 // 11. Let len be the result of calling the [[Get]] internal method of formats with argument "length".
643 let len = formats.length;
644
645 // 12. Repeat while i < len:
646 while (i < len) {
647 // a. Let format be the result of calling the [[Get]] internal method of formats with argument ToString(i).
648 let format = formats[i];
649
650 // b. Let score be 0.
651 let score = 0;
652
653 // c. For each property shown in Table 3:
654 for (let property in dateTimeComponents) {
655 if (!hop.call(dateTimeComponents, property))
656 continue;
657
658 // i. Let optionsProp be options.[[<property>]].
659 let optionsProp = options['[['+ property +']]'];
660
661 // ii. Let formatPropDesc be the result of calling the [[GetOwnProperty]] internal method of format
662 // with argument property.
663 // iii. If formatPropDesc is not undefined, then
664 // 1. Let formatProp be the result of calling the [[Get]] internal method of format with argument property.
665 let formatProp = hop.call(format, property) ? format[property] : undefined;
666
667 // iv. If optionsProp is undefined and formatProp is not undefined, then decrease score by
668 // additionPenalty.
669 if (optionsProp === undefined && formatProp !== undefined)
670 score -= additionPenalty;
671
672 // v. Else if optionsProp is not undefined and formatProp is undefined, then decrease score by
673 // removalPenalty.
674 else if (optionsProp !== undefined && formatProp === undefined)
675 score -= removalPenalty;
676
677 // vi. Else
678 else {
679 // 1. Let values be the array ["2-digit", "numeric", "narrow", "short",
680 // "long"].
681 let values = [ '2-digit', 'numeric', 'narrow', 'short', 'long' ];
682
683 // 2. Let optionsPropIndex be the index of optionsProp within values.
684 let optionsPropIndex = arrIndexOf.call(values, optionsProp);
685
686 // 3. Let formatPropIndex be the index of formatProp within values.
687 let formatPropIndex = arrIndexOf.call(values, formatProp);
688
689 // 4. Let delta be max(min(formatPropIndex - optionsPropIndex, 2), -2).
690 let delta = Math.max(Math.min(formatPropIndex - optionsPropIndex, 2), -2);
691
692 {
693 // diverging from spec
694 // When the bestFit argument is true, subtract additional penalty where data types are not the same
695 if ((formatPropIndex <= 1 && optionsPropIndex >= 2) || (formatPropIndex >= 2 && optionsPropIndex <= 1)) {
696 // 5. If delta = 2, decrease score by longMorePenalty.
697 if (delta > 0)
698 score -= longMorePenalty;
699 else if (delta < 0)
700 score -= longLessPenalty;
701 } else {
702 // 5. If delta = 2, decrease score by longMorePenalty.
703 if (delta > 1)
704 score -= shortMorePenalty;
705 else if (delta < -1)
706 score -= shortLessPenalty;
707 }
708 }
709 }
710 }
711
712 {
713 // diverging to also take into consideration differences between 12 or 24 hours
714 // which is special for the best fit only.
715 if (format._.hour12 !== options.hour12) {
716 score -= hour12Penalty;
717 }
718 }
719
720 // d. If score > bestScore, then
721 if (score > bestScore) {
722 // i. Let bestScore be score.
723 bestScore = score;
724 // ii. Let bestFormat be format.
725 bestFormat = format;
726 }
727
728 // e. Increase i by 1.
729 i++;
730 }
731
732 // 13. Return bestFormat.
733 return bestFormat;
734}
735
736/* 12.2.3 */internals.DateTimeFormat = {
737 '[[availableLocales]]': [],
738 '[[relevantExtensionKeys]]': ['ca', 'nu'],
739 '[[localeData]]': {},
740};
741
742/**
743 * When the supportedLocalesOf method of Intl.DateTimeFormat is called, the
744 * following steps are taken:
745 */
746/* 12.2.2 */
747defineProperty(Intl.DateTimeFormat, 'supportedLocalesOf', {
748 configurable: true,
749 writable: true,
750 value: fnBind.call(function (locales) {
751 // Bound functions only have the `this` value altered if being used as a constructor,
752 // this lets us imitate a native function that has no constructor
753 if (!hop.call(this, '[[availableLocales]]'))
754 throw new TypeError('supportedLocalesOf() is not a constructor');
755
756 // Create an object whose props can be used to restore the values of RegExp props
757 let regexpState = createRegExpRestore(),
758
759 // 1. If options is not provided, then let options be undefined.
760 options = arguments[1],
761
762 // 2. Let availableLocales be the value of the [[availableLocales]] internal
763 // property of the standard built-in object that is the initial value of
764 // Intl.NumberFormat.
765
766 availableLocales = this['[[availableLocales]]'],
767
768 // 3. Let requestedLocales be the result of calling the CanonicalizeLocaleList
769 // abstract operation (defined in 9.2.1) with argument locales.
770 requestedLocales = CanonicalizeLocaleList(locales);
771
772 // Restore the RegExp properties
773 regexpState.exp.test(regexpState.input);
774
775 // 4. Return the result of calling the SupportedLocales abstract operation
776 // (defined in 9.2.8) with arguments availableLocales, requestedLocales,
777 // and options.
778 return SupportedLocales(availableLocales, requestedLocales, options);
779 }, internals.NumberFormat),
780});
781
782/**
783 * This named accessor property returns a function that formats a number
784 * according to the effective locale and the formatting options of this
785 * DateTimeFormat object.
786 */
787/* 12.3.2 */defineProperty(Intl.DateTimeFormat.prototype, 'format', {
788 configurable: true,
789 get: GetFormatDateTime,
790});
791
792defineProperty(Intl.DateTimeFormat.prototype, 'formatToParts', {
793 configurable: true,
794 get: GetFormatToPartsDateTime,
795});
796
797function GetFormatDateTime() {
798 let internal = this !== null && typeof this === 'object' && getInternalProperties(this);
799
800 // Satisfy test 12.3_b
801 if (!internal || !internal['[[initializedDateTimeFormat]]'])
802 throw new TypeError('`this` value for format() is not an initialized Intl.DateTimeFormat object.');
803
804 // The value of the [[Get]] attribute is a function that takes the following
805 // steps:
806
807 // 1. If the [[boundFormat]] internal property of this DateTimeFormat object
808 // is undefined, then:
809 if (internal['[[boundFormat]]'] === undefined) {
810 // a. Let F be a Function object, with internal properties set as
811 // specified for built-in functions in ES5, 15, or successor, and the
812 // length property set to 0, that takes the argument date and
813 // performs the following steps:
814 let F = function () {
815 // i. If date is not provided or is undefined, then let x be the
816 // result as if by the expression Date.now() where Date.now is
817 // the standard built-in function defined in ES5, 15.9.4.4.
818 // ii. Else let x be ToNumber(date).
819 // iii. Return the result of calling the FormatDateTime abstract
820 // operation (defined below) with arguments this and x.
821 let x = Number(arguments.length === 0 ? Date.now() : arguments[0]);
822 return FormatDateTime(this, x);
823 };
824 // b. Let bind be the standard built-in function object defined in ES5,
825 // 15.3.4.5.
826 // c. Let bf be the result of calling the [[Call]] internal method of
827 // bind with F as the this value and an argument list containing
828 // the single item this.
829 let bf = fnBind.call(F, this);
830 // d. Set the [[boundFormat]] internal property of this NumberFormat
831 // object to bf.
832 internal['[[boundFormat]]'] = bf;
833 }
834 // Return the value of the [[boundFormat]] internal property of this
835 // NumberFormat object.
836 return internal['[[boundFormat]]'];
837}
838
839function GetFormatToPartsDateTime() {
840 let internal = this !== null && typeof this === 'object' && getInternalProperties(this);
841
842 if (!internal || !internal['[[initializedDateTimeFormat]]'])
843 throw new TypeError('`this` value for formatToParts() is not an initialized Intl.DateTimeFormat object.');
844
845 if (internal['[[boundFormatToParts]]'] === undefined) {
846 let F = function () {
847 let x = Number(arguments.length === 0 ? Date.now() : arguments[0]);
848 return FormatToPartsDateTime(this, x);
849 };
850 let bf = fnBind.call(F, this);
851 internal['[[boundFormatToParts]]'] = bf;
852 }
853 return internal['[[boundFormatToParts]]'];
854}
855
856function CreateDateTimeParts(dateTimeFormat, x) {
857 // 1. If x is not a finite Number, then throw a RangeError exception.
858 if (!isFinite(x))
859 throw new RangeError('Invalid valid date passed to format');
860
861 let internal = dateTimeFormat.__getInternalProperties(secret);
862
863 // Creating restore point for properties on the RegExp object... please wait
864 /* let regexpState = */createRegExpRestore(); // ###TODO: review this
865
866 // 2. Let locale be the value of the [[locale]] internal property of dateTimeFormat.
867 let locale = internal['[[locale]]'];
868
869 // 3. Let nf be the result of creating a new NumberFormat object as if by the
870 // expression new Intl.NumberFormat([locale], {useGrouping: false}) where
871 // Intl.NumberFormat is the standard built-in constructor defined in 11.1.3.
872 let nf = new Intl.NumberFormat([locale], {useGrouping: false});
873
874 // 4. Let nf2 be the result of creating a new NumberFormat object as if by the
875 // expression new Intl.NumberFormat([locale], {minimumIntegerDigits: 2, useGrouping:
876 // false}) where Intl.NumberFormat is the standard built-in constructor defined in
877 // 11.1.3.
878 let nf2 = new Intl.NumberFormat([locale], {minimumIntegerDigits: 2, useGrouping: false});
879
880 // 5. Let tm be the result of calling the ToLocalTime abstract operation (defined
881 // below) with x, the value of the [[calendar]] internal property of dateTimeFormat,
882 // and the value of the [[timeZone]] internal property of dateTimeFormat.
883 let tm = ToLocalTime(x, internal['[[calendar]]'], internal['[[timeZone]]']);
884
885 // 6. Let result be the value of the [[pattern]] internal property of dateTimeFormat.
886 let pattern = internal['[[pattern]]'];
887
888 // 7.
889 let result = new List();
890
891 // 8.
892 let index = 0;
893
894 // 9.
895 let beginIndex = pattern.indexOf('{');
896
897 // 10.
898 let endIndex = 0;
899
900 // Need the locale minus any extensions
901 let dataLocale = internal['[[dataLocale]]'];
902
903 // Need the calendar data from CLDR
904 let localeData = internals.DateTimeFormat['[[localeData]]'][dataLocale].calendars;
905 let ca = internal['[[calendar]]'];
906
907 // 11.
908 while (beginIndex !== -1) {
909 let fv;
910 // a.
911 endIndex = pattern.indexOf('}', beginIndex);
912 // b.
913 if (endIndex === -1) {
914 throw new Error('Unclosed pattern');
915 }
916 // c.
917 if (beginIndex > index) {
918 arrPush.call(result, {
919 type: 'literal',
920 value: pattern.substring(index, beginIndex),
921 });
922 }
923 // d.
924 let p = pattern.substring(beginIndex + 1, endIndex);
925 // e.
926 if (dateTimeComponents.hasOwnProperty(p)) {
927 // i. Let f be the value of the [[<p>]] internal property of dateTimeFormat.
928 let f = internal['[['+ p +']]'];
929 // ii. Let v be the value of tm.[[<p>]].
930 let v = tm['[['+ p +']]'];
931 // iii. If p is "year" and v ≤ 0, then let v be 1 - v.
932 if (p === 'year' && v <= 0) {
933 v = 1 - v;
934 }
935 // iv. If p is "month", then increase v by 1.
936 else if (p === 'month') {
937 v++;
938 }
939 // v. If p is "hour" and the value of the [[hour12]] internal property of
940 // dateTimeFormat is true, then
941 else if (p === 'hour' && internal['[[hour12]]'] === true) {
942 // 1. Let v be v modulo 12.
943 v = v % 12;
944 // 2. If v is 0 and the value of the [[hourNo0]] internal property of
945 // dateTimeFormat is true, then let v be 12.
946 if (v === 0 && internal['[[hourNo0]]'] === true) {
947 v = 12;
948 }
949 }
950
951 // vi. If f is "numeric", then
952 if (f === 'numeric') {
953 // 1. Let fv be the result of calling the FormatNumber abstract operation
954 // (defined in 11.3.2) with arguments nf and v.
955 fv = FormatNumber(nf, v);
956 }
957 // vii. Else if f is "2-digit", then
958 else if (f === '2-digit') {
959 // 1. Let fv be the result of calling the FormatNumber abstract operation
960 // with arguments nf2 and v.
961 fv = FormatNumber(nf2, v);
962 // 2. If the length of fv is greater than 2, let fv be the substring of fv
963 // containing the last two characters.
964 if (fv.length > 2) {
965 fv = fv.slice(-2);
966 }
967 }
968 // viii. Else if f is "narrow", "short", or "long", then let fv be a String
969 // value representing f in the desired form; the String value depends upon
970 // the implementation and the effective locale and calendar of
971 // dateTimeFormat. If p is "month", then the String value may also depend
972 // on whether dateTimeFormat has a [[day]] internal property. If p is
973 // "timeZoneName", then the String value may also depend on the value of
974 // the [[inDST]] field of tm.
975 else if (f in dateWidths) {
976 switch (p) {
977 case 'month':
978 fv = resolveDateString(localeData, ca, 'months', f, tm['[['+ p +']]']);
979 break;
980
981 case 'weekday':
982 try {
983 fv = resolveDateString(localeData, ca, 'days', f, tm['[['+ p +']]']);
984 // fv = resolveDateString(ca.days, f)[tm['[['+ p +']]']];
985 } catch (e) {
986 throw new Error('Could not find weekday data for locale '+locale);
987 }
988 break;
989
990 case 'timeZoneName':
991 fv = ''; // ###TODO
992 break;
993
994 case 'era':
995 try {
996 fv = resolveDateString(localeData, ca, 'eras', f, tm['[['+ p +']]']);
997 } catch (e) {
998 throw new Error('Could not find era data for locale '+locale);
999 }
1000 break;
1001
1002 default:
1003 fv = tm['[['+ p +']]'];
1004 }
1005 }
1006 // ix
1007 arrPush.call(result, {
1008 type: p,
1009 value: fv,
1010 });
1011 // f.
1012 } else if (p === 'ampm') {
1013 // i.
1014 let v = tm['[[hour]]'];
1015 // ii./iii.
1016 fv = resolveDateString(localeData, ca, 'dayPeriods', v > 11 ? 'pm' : 'am', null);
1017 // iv.
1018 arrPush.call(result, {
1019 type: 'dayPeriod',
1020 value: fv,
1021 });
1022 // g.
1023 } else {
1024 arrPush.call(result, {
1025 type: 'literal',
1026 value: pattern.substring(beginIndex, endIndex + 1),
1027 });
1028 }
1029 // h.
1030 index = endIndex + 1;
1031 // i.
1032 beginIndex = pattern.indexOf('{', index);
1033 }
1034 // 12.
1035 if (endIndex < pattern.length - 1) {
1036 arrPush.call(result, {
1037 type: 'literal',
1038 value: pattern.substr(endIndex + 1),
1039 });
1040 }
1041 // 13.
1042 return result;
1043}
1044
1045/**
1046 * When the FormatDateTime abstract operation is called with arguments dateTimeFormat
1047 * (which must be an object initialized as a DateTimeFormat) and x (which must be a Number
1048 * value), it returns a String value representing x (interpreted as a time value as
1049 * specified in ES5, 15.9.1.1) according to the effective locale and the formatting
1050 * options of dateTimeFormat.
1051 */
1052export function FormatDateTime(dateTimeFormat, x) {
1053 let parts = CreateDateTimeParts(dateTimeFormat, x);
1054 let result = '';
1055
1056 for (let i = 0; parts.length > i; i++) {
1057 let part = parts[i];
1058 result += part.value;
1059 }
1060 return result;
1061}
1062
1063function FormatToPartsDateTime(dateTimeFormat, x) {
1064 let parts = CreateDateTimeParts(dateTimeFormat, x);
1065 let result = [];
1066 for (let i = 0; parts.length > i; i++) {
1067 let part = parts[i];
1068 result.push({
1069 type: part.type,
1070 value: part.value,
1071 });
1072 }
1073 return result;
1074}
1075
1076
1077/**
1078 * When the ToLocalTime abstract operation is called with arguments date, calendar, and
1079 * timeZone, the following steps are taken:
1080 */
1081function ToLocalTime(date, calendar, timeZone) {
1082 // 1. Apply calendrical calculations on date for the given calendar and time zone to
1083 // produce weekday, era, year, month, day, hour, minute, second, and inDST values.
1084 // The calculations should use best available information about the specified
1085 // calendar and time zone. If the calendar is "gregory", then the calculations must
1086 // match the algorithms specified in ES5, 15.9.1, except that calculations are not
1087 // bound by the restrictions on the use of best available information on time zones
1088 // for local time zone adjustment and daylight saving time adjustment imposed by
1089 // ES5, 15.9.1.7 and 15.9.1.8.
1090 // ###TODO###
1091 let d = new Date(date),
1092 m = 'get' + (timeZone || '');
1093
1094 // 2. Return a Record with fields [[weekday]], [[era]], [[year]], [[month]], [[day]],
1095 // [[hour]], [[minute]], [[second]], and [[inDST]], each with the corresponding
1096 // calculated value.
1097 return new Record({
1098 '[[weekday]]': d[m + 'Day'](),
1099 '[[era]]' : +(d[m + 'FullYear']() >= 0),
1100 '[[year]]' : d[m + 'FullYear'](),
1101 '[[month]]' : d[m + 'Month'](),
1102 '[[day]]' : d[m + 'Date'](),
1103 '[[hour]]' : d[m + 'Hours'](),
1104 '[[minute]]' : d[m + 'Minutes'](),
1105 '[[second]]' : d[m + 'Seconds'](),
1106 '[[inDST]]' : false, // ###TODO###
1107 });
1108}
1109
1110/**
1111 * The function returns a new object whose properties and attributes are set as if
1112 * constructed by an object literal assigning to each of the following properties the
1113 * value of the corresponding internal property of this DateTimeFormat object (see 12.4):
1114 * locale, calendar, numberingSystem, timeZone, hour12, weekday, era, year, month, day,
1115 * hour, minute, second, and timeZoneName. Properties whose corresponding internal
1116 * properties are not present are not assigned.
1117 */
1118/* 12.3.3 */defineProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', {
1119 writable: true,
1120 configurable: true,
1121 value: function () {
1122 let prop,
1123 descs = new Record(),
1124 props = [
1125 'locale', 'calendar', 'numberingSystem', 'timeZone', 'hour12', 'weekday',
1126 'era', 'year', 'month', 'day', 'hour', 'minute', 'second', 'timeZoneName',
1127 ],
1128 internal = this !== null && typeof this === 'object' && getInternalProperties(this);
1129
1130 // Satisfy test 12.3_b
1131 if (!internal || !internal['[[initializedDateTimeFormat]]'])
1132 throw new TypeError('`this` value for resolvedOptions() is not an initialized Intl.DateTimeFormat object.');
1133
1134 for (let i = 0, max = props.length; i < max; i++) {
1135 if (hop.call(internal, prop = '[[' + props[i] + ']]'))
1136 descs[props[i]] = { value: internal[prop], writable: true, configurable: true, enumerable: true };
1137 }
1138
1139 return objCreate({}, descs);
1140 },
1141});