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, ''); | |
arv (Not doing code reviews)
2013/07/08 17:59:23
What if replace has been overridden? Same goes for
| |
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]); | |
arv (Not doing code reviews)
2013/07/08 17:59:23
Needs to be the original Object and original apply
| |
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 native function NativeJSCreateDateTimeFormat(); | |
239 | |
240 if (dateFormat.hasOwnProperty('__initializedIntlObject')) { | |
241 throw new TypeError('Trying to re-initialize DateTimeFormat object.'); | |
242 } | |
243 | |
244 if (options === undefined) { | |
245 options = {}; | |
246 } | |
247 | |
248 var locale = resolveLocale('dateformat', locales, options); | |
249 | |
250 options = toDateTimeOptions(options, 'any', 'date'); | |
251 | |
252 var getOption = getGetOption(options, 'dateformat'); | |
253 | |
254 // We implement only best fit algorithm, but still need to check | |
255 // if the formatMatcher values are in range. | |
256 var matcher = getOption('formatMatcher', 'string', | |
257 ['basic', 'best fit'], 'best fit'); | |
258 | |
259 // Build LDML string for the skeleton that we pass to the formatter. | |
260 var ldmlString = toLDMLString(options); | |
261 | |
262 // Filter out supported extension keys so we know what to put in resolved | |
263 // section later on. | |
264 // We need to pass calendar and number system to the method. | |
265 var tz = canonicalizeTimeZoneID(options.timeZone); | |
266 | |
267 // ICU prefers options to be passed using -u- extension key/values, so | |
268 // we need to build that. | |
269 var internalOptions = {}; | |
270 var extensionMap = parseExtension(locale.extension); | |
271 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP, | |
272 getOption, internalOptions); | |
273 | |
274 var requestedLocale = locale.locale + extension; | |
275 var resolved = Object.defineProperties({}, { | |
276 calendar: {writable: true}, | |
277 day: {writable: true}, | |
278 era: {writable: true}, | |
279 hour12: {writable: true}, | |
280 hour: {writable: true}, | |
281 locale: {writable: true}, | |
282 minute: {writable: true}, | |
283 month: {writable: true}, | |
284 numberingSystem: {writable: true}, | |
285 pattern: {writable: true}, | |
286 requestedLocale: {value: requestedLocale, writable: true}, | |
287 second: {writable: true}, | |
288 timeZone: {writable: true}, | |
289 timeZoneName: {writable: true}, | |
290 tz: {value: tz, writable: true}, | |
291 weekday: {writable: true}, | |
292 year: {writable: true} | |
293 }); | |
294 | |
295 var formatter = NativeJSCreateDateTimeFormat( | |
296 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved); | |
297 | |
298 if (tz !== undefined && tz !== resolved.timeZone) { | |
299 throw new RangeError('Unsupported time zone specified ' + tz); | |
300 } | |
301 | |
302 Object.defineProperty(dateFormat, 'formatter', {value: formatter}); | |
303 Object.defineProperty(dateFormat, 'resolved', {value: resolved}); | |
304 Object.defineProperty(dateFormat, '__initializedIntlObject', | |
305 {value: 'dateformat'}); | |
306 | |
307 return dateFormat; | |
308 } | |
309 | |
310 | |
311 /** | |
312 * Constructs Intl.DateTimeFormat object given optional locales and options | |
313 * parameters. | |
314 * | |
315 * @constructor | |
316 */ | |
317 %SetProperty(Intl, 'DateTimeFormat', function() { | |
318 var locales = arguments[0]; | |
319 var options = arguments[1]; | |
320 | |
321 if (!this || this === Intl) { | |
322 // Constructor is called as a function. | |
323 return new Intl.DateTimeFormat(locales, options); | |
324 } | |
325 | |
326 return initializeDateTimeFormat(toObject(this), locales, options); | |
327 }, | |
328 ATTRIBUTES.DONT_ENUM | |
329 ); | |
330 | |
331 | |
332 /** | |
333 * DateTimeFormat resolvedOptions method. | |
334 */ | |
335 %SetProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() { | |
336 if (%_IsConstructCall()) { | |
337 throw new TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); | |
338 } | |
339 | |
340 if (!this || typeof this !== 'object' || | |
341 this.__initializedIntlObject !== 'dateformat') { | |
342 throw new TypeError('resolvedOptions method called on a non-object or ' + | |
343 'on a object that is not Intl.DateTimeFormat.'); | |
344 } | |
345 | |
346 var format = this; | |
347 var fromPattern = fromLDMLString(format.resolved.pattern); | |
348 var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar]; | |
349 if (userCalendar === undefined) { | |
350 // Use ICU name if we don't have a match. It shouldn't happen, but | |
351 // it would be too strict to throw for this. | |
352 userCalendar = format.resolved.calendar; | |
353 } | |
354 | |
355 var locale = getOptimalLanguageTag(format.resolved.requestedLocale, | |
356 format.resolved.locale); | |
357 | |
358 var result = { | |
359 locale: locale, | |
360 numberingSystem: format.resolved.numberingSystem, | |
361 calendar: userCalendar, | |
362 timeZone: format.resolved.timeZone | |
363 }; | |
364 | |
365 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName); | |
366 addWECPropertyIfDefined(result, 'era', fromPattern.era); | |
367 addWECPropertyIfDefined(result, 'year', fromPattern.year); | |
368 addWECPropertyIfDefined(result, 'month', fromPattern.month); | |
369 addWECPropertyIfDefined(result, 'day', fromPattern.day); | |
370 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday); | |
371 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12); | |
372 addWECPropertyIfDefined(result, 'hour', fromPattern.hour); | |
373 addWECPropertyIfDefined(result, 'minute', fromPattern.minute); | |
374 addWECPropertyIfDefined(result, 'second', fromPattern.second); | |
375 | |
376 return result; | |
377 }, | |
378 ATTRIBUTES.DONT_ENUM | |
379 ); | |
380 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions); | |
381 | |
382 | |
383 /** | |
384 * Returns the subset of the given locale list for which this locale list | |
385 * has a matching (possibly fallback) locale. Locales appear in the same | |
386 * order in the returned list as in the input list. | |
387 * Options are optional parameter. | |
388 */ | |
389 %SetProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) { | |
390 if (%_IsConstructCall()) { | |
391 throw new TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); | |
392 } | |
393 | |
394 return supportedLocalesOf('dateformat', locales, arguments[1]); | |
395 }, | |
396 ATTRIBUTES.DONT_ENUM | |
397 ); | |
398 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf); | |
399 | |
400 | |
401 /** | |
402 * Returns a String value representing the result of calling ToNumber(date) | |
403 * according to the effective locale and the formatting options of this | |
404 * DateTimeFormat. | |
405 */ | |
406 function formatDate(formatter, dateValue) { | |
407 native function NativeJSInternalDateFormat(); | |
408 | |
409 var dateMs; | |
410 if (dateValue === undefined) { | |
411 dateMs = Date.now(); | |
412 } else { | |
413 dateMs = Number(dateValue); | |
414 } | |
415 | |
416 if (!isFinite(dateMs)) { | |
417 throw new RangeError('Provided date is not in valid range.'); | |
418 } | |
419 | |
420 return NativeJSInternalDateFormat(formatter.formatter, new Date(dateMs)); | |
421 } | |
422 | |
423 | |
424 /** | |
425 * Returns a Date object representing the result of calling ToString(value) | |
426 * according to the effective locale and the formatting options of this | |
427 * DateTimeFormat. | |
428 * Returns undefined if date string cannot be parsed. | |
429 */ | |
430 function parseDate(formatter, value) { | |
431 native function NativeJSInternalDateParse(); | |
432 return NativeJSInternalDateParse(formatter.formatter, String(value)); | |
433 } | |
434 | |
435 | |
436 // 0 because date is optional argument. | |
437 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0); | |
438 addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1); | |
439 | |
440 | |
441 /** | |
442 * Returns canonical Area/Location name, or throws an exception if the zone | |
443 * name is invalid IANA name. | |
444 */ | |
445 function canonicalizeTimeZoneID(tzID) { | |
446 // Skip undefined zones. | |
447 if (tzID === undefined) { | |
448 return tzID; | |
449 } | |
450 | |
451 // Special case handling (UTC, GMT). | |
452 var upperID = tzID.toUpperCase(); | |
453 if (upperID === 'UTC' || upperID === 'GMT' || | |
454 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') { | |
455 return 'UTC'; | |
456 } | |
457 | |
458 // We expect only _ and / beside ASCII letters. | |
459 // All inputs should conform to Area/Location from now on. | |
460 var match = TIMEZONE_NAME_CHECK_RE.exec(tzID); | |
461 if (match === null) { | |
462 throw new RangeError('Expected Area/Location for time zone, got ' + tzID); | |
463 } | |
464 | |
465 var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]); | |
466 var i = 3; | |
467 while (match[i] !== undefined && i < match.length) { | |
468 result = result + '_' + toTitleCaseWord(match[i]); | |
469 i++; | |
470 } | |
471 | |
472 return result; | |
473 } | |
OLD | NEW |