OLD | NEW |
| (Empty) |
1 // Copyright 2013 the V8 project authors. All rights reserved. | |
2 // Redistribution and use in source and binary forms, with or without | |
3 // modification, are permitted provided that the following conditions are | |
4 // met: | |
5 // | |
6 // * Redistributions of source code must retain the above copyright | |
7 // notice, this list of conditions and the following disclaimer. | |
8 // * Redistributions in binary form must reproduce the above | |
9 // copyright notice, this list of conditions and the following | |
10 // disclaimer in the documentation and/or other materials provided | |
11 // with the distribution. | |
12 // * Neither the name of Google Inc. nor the names of its | |
13 // contributors may be used to endorse or promote products derived | |
14 // from this software without specific prior written permission. | |
15 // | |
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
27 // limitations under the License. | |
28 | |
29 // ECMAScript 402 API implementation is broken into separate files for | |
30 // each service. The build system combines them together into one | |
31 // Intl namespace. | |
32 | |
33 /** | |
34 * Returns a string that matches LDML representation of the options object. | |
35 */ | |
36 function toLDMLString(options) { | |
37 var getOption = getGetOption(options, 'dateformat'); | |
38 | |
39 var ldmlString = ''; | |
40 | |
41 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']); | |
42 ldmlString += appendToLDMLString( | |
43 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'}); | |
44 | |
45 option = getOption('era', 'string', ['narrow', 'short', 'long']); | |
46 ldmlString += appendToLDMLString( | |
47 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'}); | |
48 | |
49 option = getOption('year', 'string', ['2-digit', 'numeric']); | |
50 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'}); | |
51 | |
52 option = getOption('month', 'string', | |
53 ['2-digit', 'numeric', 'narrow', 'short', 'long']); | |
54 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M', | |
55 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'}); | |
56 | |
57 option = getOption('day', 'string', ['2-digit', 'numeric']); | |
58 ldmlString += appendToLDMLString( | |
59 option, {'2-digit': 'dd', 'numeric': 'd'}); | |
60 | |
61 var hr12 = getOption('hour12', 'boolean'); | |
62 option = getOption('hour', 'string', ['2-digit', 'numeric']); | |
63 if (hr12 === undefined) { | |
64 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'}); | |
65 } else if (hr12 === true) { | |
66 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'}); | |
67 } else { | |
68 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'}); | |
69 } | |
70 | |
71 option = getOption('minute', 'string', ['2-digit', 'numeric']); | |
72 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'}); | |
73 | |
74 option = getOption('second', 'string', ['2-digit', 'numeric']); | |
75 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'}); | |
76 | |
77 option = getOption('timeZoneName', 'string', ['short', 'long']); | |
78 ldmlString += appendToLDMLString(option, {short: 'v', long: 'vv'}); | |
79 | |
80 return ldmlString; | |
81 } | |
82 | |
83 | |
84 /** | |
85 * Returns either LDML equivalent of the current option or empty string. | |
86 */ | |
87 function appendToLDMLString(option, pairs) { | |
88 if (option !== undefined) { | |
89 return pairs[option]; | |
90 } else { | |
91 return ''; | |
92 } | |
93 } | |
94 | |
95 | |
96 /** | |
97 * Returns object that matches LDML representation of the date. | |
98 */ | |
99 function fromLDMLString(ldmlString) { | |
100 // First remove '' quoted text, so we lose 'Uhr' strings. | |
101 ldmlString = ldmlString.replace(QUOTED_STRING_RE, ''); | |
102 | |
103 var options = {}; | |
104 var match = ldmlString.match(/E{3,5}/g); | |
105 options = appendToDateTimeObject( | |
106 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'}); | |
107 | |
108 match = ldmlString.match(/G{3,5}/g); | |
109 options = appendToDateTimeObject( | |
110 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'}); | |
111 | |
112 match = ldmlString.match(/y{1,2}/g); | |
113 options = appendToDateTimeObject( | |
114 options, 'year', match, {y: 'numeric', yy: '2-digit'}); | |
115 | |
116 match = ldmlString.match(/M{1,5}/g); | |
117 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit', | |
118 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'}); | |
119 | |
120 // Sometimes we get L instead of M for month - standalone name. | |
121 match = ldmlString.match(/L{1,5}/g); | |
122 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit', | |
123 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'}); | |
124 | |
125 match = ldmlString.match(/d{1,2}/g); | |
126 options = appendToDateTimeObject( | |
127 options, 'day', match, {d: 'numeric', dd: '2-digit'}); | |
128 | |
129 match = ldmlString.match(/h{1,2}/g); | |
130 if (match !== null) { | |
131 options['hour12'] = true; | |
132 } | |
133 options = appendToDateTimeObject( | |
134 options, 'hour', match, {h: 'numeric', hh: '2-digit'}); | |
135 | |
136 match = ldmlString.match(/H{1,2}/g); | |
137 if (match !== null) { | |
138 options['hour12'] = false; | |
139 } | |
140 options = appendToDateTimeObject( | |
141 options, 'hour', match, {H: 'numeric', HH: '2-digit'}); | |
142 | |
143 match = ldmlString.match(/m{1,2}/g); | |
144 options = appendToDateTimeObject( | |
145 options, 'minute', match, {m: 'numeric', mm: '2-digit'}); | |
146 | |
147 match = ldmlString.match(/s{1,2}/g); | |
148 options = appendToDateTimeObject( | |
149 options, 'second', match, {s: 'numeric', ss: '2-digit'}); | |
150 | |
151 match = ldmlString.match(/v{1,2}/g); | |
152 options = appendToDateTimeObject( | |
153 options, 'timeZoneName', match, {v: 'short', vv: 'long'}); | |
154 | |
155 return options; | |
156 } | |
157 | |
158 | |
159 function appendToDateTimeObject(options, option, match, pairs) { | |
160 if (match === null) { | |
161 if (!options.hasOwnProperty(option)) { | |
162 defineWEProperty(options, option, undefined); | |
163 } | |
164 return options; | |
165 } | |
166 | |
167 var property = match[0]; | |
168 defineWEProperty(options, option, pairs[property]); | |
169 | |
170 return options; | |
171 } | |
172 | |
173 | |
174 /** | |
175 * Returns options with at least default values in it. | |
176 */ | |
177 function toDateTimeOptions(options, required, defaults) { | |
178 if (options === undefined) { | |
179 options = null; | |
180 } else { | |
181 options = toObject(options); | |
182 } | |
183 | |
184 options = Object.apply(this, [options]); | |
185 | |
186 var needsDefault = true; | |
187 if ((required === 'date' || required === 'any') && | |
188 (options.weekday !== undefined || options.year !== undefined || | |
189 options.month !== undefined || options.day !== undefined)) { | |
190 needsDefault = false; | |
191 } | |
192 | |
193 if ((required === 'time' || required === 'any') && | |
194 (options.hour !== undefined || options.minute !== undefined || | |
195 options.second !== undefined)) { | |
196 needsDefault = false; | |
197 } | |
198 | |
199 if (needsDefault && (defaults === 'date' || defaults === 'all')) { | |
200 Object.defineProperty(options, 'year', {value: 'numeric', | |
201 writable: true, | |
202 enumerable: true, | |
203 configurable: true}); | |
204 Object.defineProperty(options, 'month', {value: 'numeric', | |
205 writable: true, | |
206 enumerable: true, | |
207 configurable: true}); | |
208 Object.defineProperty(options, 'day', {value: 'numeric', | |
209 writable: true, | |
210 enumerable: true, | |
211 configurable: true}); | |
212 } | |
213 | |
214 if (needsDefault && (defaults === 'time' || defaults === 'all')) { | |
215 Object.defineProperty(options, 'hour', {value: 'numeric', | |
216 writable: true, | |
217 enumerable: true, | |
218 configurable: true}); | |
219 Object.defineProperty(options, 'minute', {value: 'numeric', | |
220 writable: true, | |
221 enumerable: true, | |
222 configurable: true}); | |
223 Object.defineProperty(options, 'second', {value: 'numeric', | |
224 writable: true, | |
225 enumerable: true, | |
226 configurable: true}); | |
227 } | |
228 | |
229 return options; | |
230 } | |
231 | |
232 | |
233 /** | |
234 * Initializes the given object so it's a valid DateTimeFormat instance. | |
235 * Useful for subclassing. | |
236 */ | |
237 function initializeDateTimeFormat(dateFormat, locales, options) { | |
238 | |
239 if (dateFormat.hasOwnProperty('__initializedIntlObject')) { | |
240 throw new TypeError('Trying to re-initialize DateTimeFormat object.'); | |
241 } | |
242 | |
243 if (options === undefined) { | |
244 options = {}; | |
245 } | |
246 | |
247 var locale = resolveLocale('dateformat', locales, options); | |
248 | |
249 options = toDateTimeOptions(options, 'any', 'date'); | |
250 | |
251 var getOption = getGetOption(options, 'dateformat'); | |
252 | |
253 // We implement only best fit algorithm, but still need to check | |
254 // if the formatMatcher values are in range. | |
255 var matcher = getOption('formatMatcher', 'string', | |
256 ['basic', 'best fit'], 'best fit'); | |
257 | |
258 // Build LDML string for the skeleton that we pass to the formatter. | |
259 var ldmlString = toLDMLString(options); | |
260 | |
261 // Filter out supported extension keys so we know what to put in resolved | |
262 // section later on. | |
263 // We need to pass calendar and number system to the method. | |
264 var tz = canonicalizeTimeZoneID(options.timeZone); | |
265 | |
266 // ICU prefers options to be passed using -u- extension key/values, so | |
267 // we need to build that. | |
268 var internalOptions = {}; | |
269 var extensionMap = parseExtension(locale.extension); | |
270 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP, | |
271 getOption, internalOptions); | |
272 | |
273 var requestedLocale = locale.locale + extension; | |
274 var resolved = Object.defineProperties({}, { | |
275 calendar: {writable: true}, | |
276 day: {writable: true}, | |
277 era: {writable: true}, | |
278 hour12: {writable: true}, | |
279 hour: {writable: true}, | |
280 locale: {writable: true}, | |
281 minute: {writable: true}, | |
282 month: {writable: true}, | |
283 numberingSystem: {writable: true}, | |
284 pattern: {writable: true}, | |
285 requestedLocale: {value: requestedLocale, writable: true}, | |
286 second: {writable: true}, | |
287 timeZone: {writable: true}, | |
288 timeZoneName: {writable: true}, | |
289 tz: {value: tz, writable: true}, | |
290 weekday: {writable: true}, | |
291 year: {writable: true} | |
292 }); | |
293 | |
294 var formatter = %CreateDateTimeFormat( | |
295 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved); | |
296 | |
297 if (tz !== undefined && tz !== resolved.timeZone) { | |
298 throw new RangeError('Unsupported time zone specified ' + tz); | |
299 } | |
300 | |
301 Object.defineProperty(dateFormat, 'formatter', {value: formatter}); | |
302 Object.defineProperty(dateFormat, 'resolved', {value: resolved}); | |
303 Object.defineProperty(dateFormat, '__initializedIntlObject', | |
304 {value: 'dateformat'}); | |
305 | |
306 return dateFormat; | |
307 } | |
308 | |
309 | |
310 /** | |
311 * Constructs Intl.DateTimeFormat object given optional locales and options | |
312 * parameters. | |
313 * | |
314 * @constructor | |
315 */ | |
316 %SetProperty(Intl, 'DateTimeFormat', function() { | |
317 var locales = arguments[0]; | |
318 var options = arguments[1]; | |
319 | |
320 if (!this || this === Intl) { | |
321 // Constructor is called as a function. | |
322 return new Intl.DateTimeFormat(locales, options); | |
323 } | |
324 | |
325 return initializeDateTimeFormat(toObject(this), locales, options); | |
326 }, | |
327 ATTRIBUTES.DONT_ENUM | |
328 ); | |
329 | |
330 | |
331 /** | |
332 * DateTimeFormat resolvedOptions method. | |
333 */ | |
334 %SetProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() { | |
335 if (%_IsConstructCall()) { | |
336 throw new TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); | |
337 } | |
338 | |
339 if (!this || typeof this !== 'object' || | |
340 this.__initializedIntlObject !== 'dateformat') { | |
341 throw new TypeError('resolvedOptions method called on a non-object or ' + | |
342 'on a object that is not Intl.DateTimeFormat.'); | |
343 } | |
344 | |
345 var format = this; | |
346 var fromPattern = fromLDMLString(format.resolved.pattern); | |
347 var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar]; | |
348 if (userCalendar === undefined) { | |
349 // Use ICU name if we don't have a match. It shouldn't happen, but | |
350 // it would be too strict to throw for this. | |
351 userCalendar = format.resolved.calendar; | |
352 } | |
353 | |
354 var locale = getOptimalLanguageTag(format.resolved.requestedLocale, | |
355 format.resolved.locale); | |
356 | |
357 var result = { | |
358 locale: locale, | |
359 numberingSystem: format.resolved.numberingSystem, | |
360 calendar: userCalendar, | |
361 timeZone: format.resolved.timeZone | |
362 }; | |
363 | |
364 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName); | |
365 addWECPropertyIfDefined(result, 'era', fromPattern.era); | |
366 addWECPropertyIfDefined(result, 'year', fromPattern.year); | |
367 addWECPropertyIfDefined(result, 'month', fromPattern.month); | |
368 addWECPropertyIfDefined(result, 'day', fromPattern.day); | |
369 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday); | |
370 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12); | |
371 addWECPropertyIfDefined(result, 'hour', fromPattern.hour); | |
372 addWECPropertyIfDefined(result, 'minute', fromPattern.minute); | |
373 addWECPropertyIfDefined(result, 'second', fromPattern.second); | |
374 | |
375 return result; | |
376 }, | |
377 ATTRIBUTES.DONT_ENUM | |
378 ); | |
379 %FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions, | |
380 'resolvedOptions'); | |
381 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions); | |
382 %SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions); | |
383 | |
384 | |
385 /** | |
386 * Returns the subset of the given locale list for which this locale list | |
387 * has a matching (possibly fallback) locale. Locales appear in the same | |
388 * order in the returned list as in the input list. | |
389 * Options are optional parameter. | |
390 */ | |
391 %SetProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) { | |
392 if (%_IsConstructCall()) { | |
393 throw new TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); | |
394 } | |
395 | |
396 return supportedLocalesOf('dateformat', locales, arguments[1]); | |
397 }, | |
398 ATTRIBUTES.DONT_ENUM | |
399 ); | |
400 %FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf'); | |
401 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf); | |
402 %SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf); | |
403 | |
404 | |
405 /** | |
406 * Returns a String value representing the result of calling ToNumber(date) | |
407 * according to the effective locale and the formatting options of this | |
408 * DateTimeFormat. | |
409 */ | |
410 function formatDate(formatter, dateValue) { | |
411 var dateMs; | |
412 if (dateValue === undefined) { | |
413 dateMs = Date.now(); | |
414 } else { | |
415 dateMs = Number(dateValue); | |
416 } | |
417 | |
418 if (!isFinite(dateMs)) { | |
419 throw new RangeError('Provided date is not in valid range.'); | |
420 } | |
421 | |
422 return %InternalDateFormat(formatter.formatter, new Date(dateMs)); | |
423 } | |
424 | |
425 | |
426 /** | |
427 * Returns a Date object representing the result of calling ToString(value) | |
428 * according to the effective locale and the formatting options of this | |
429 * DateTimeFormat. | |
430 * Returns undefined if date string cannot be parsed. | |
431 */ | |
432 function parseDate(formatter, value) { | |
433 return %InternalDateParse(formatter.formatter, String(value)); | |
434 } | |
435 | |
436 | |
437 // 0 because date is optional argument. | |
438 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0); | |
439 addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1); | |
440 | |
441 | |
442 /** | |
443 * Returns canonical Area/Location name, or throws an exception if the zone | |
444 * name is invalid IANA name. | |
445 */ | |
446 function canonicalizeTimeZoneID(tzID) { | |
447 // Skip undefined zones. | |
448 if (tzID === undefined) { | |
449 return tzID; | |
450 } | |
451 | |
452 // Special case handling (UTC, GMT). | |
453 var upperID = tzID.toUpperCase(); | |
454 if (upperID === 'UTC' || upperID === 'GMT' || | |
455 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') { | |
456 return 'UTC'; | |
457 } | |
458 | |
459 // We expect only _ and / beside ASCII letters. | |
460 // All inputs should conform to Area/Location from now on. | |
461 var match = TIMEZONE_NAME_CHECK_RE.exec(tzID); | |
462 if (match === null) { | |
463 throw new RangeError('Expected Area/Location for time zone, got ' + tzID); | |
464 } | |
465 | |
466 var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]); | |
467 var i = 3; | |
468 while (match[i] !== undefined && i < match.length) { | |
469 result = result + '_' + toTitleCaseWord(match[i]); | |
470 i++; | |
471 } | |
472 | |
473 return result; | |
474 } | |
OLD | NEW |