Get rid of HeapRootVisitor and make SlotVisitor less painful to use
[WebKit-https.git] / Source / JavaScriptCore / runtime / IntlDateTimeFormat.cpp
1 /*
2  * Copyright (C) 2015 Andy VanWagoner (thetalecrafter@gmail.com)
3  * Copyright (C) 2016 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "IntlDateTimeFormat.h"
29
30 #if ENABLE(INTL)
31
32 #include "DateInstance.h"
33 #include "Error.h"
34 #include "IntlDateTimeFormatConstructor.h"
35 #include "IntlObject.h"
36 #include "JSBoundFunction.h"
37 #include "JSCInlines.h"
38 #include "ObjectConstructor.h"
39 #include <unicode/ucal.h>
40 #include <unicode/udatpg.h>
41 #include <unicode/uenum.h>
42 #include <wtf/text/StringBuilder.h>
43
44 namespace JSC {
45
46 const ClassInfo IntlDateTimeFormat::s_info = { "Object", &Base::s_info, 0, CREATE_METHOD_TABLE(IntlDateTimeFormat) };
47
48 static const char* const relevantExtensionKeys[2] = { "ca", "nu" };
49 static const size_t indexOfExtensionKeyCa = 0;
50 static const size_t indexOfExtensionKeyNu = 1;
51
52 void IntlDateTimeFormat::UDateFormatDeleter::operator()(UDateFormat* dateFormat) const
53 {
54     if (dateFormat)
55         udat_close(dateFormat);
56 }
57
58 IntlDateTimeFormat* IntlDateTimeFormat::create(VM& vm, Structure* structure)
59 {
60     IntlDateTimeFormat* format = new (NotNull, allocateCell<IntlDateTimeFormat>(vm.heap)) IntlDateTimeFormat(vm, structure);
61     format->finishCreation(vm);
62     return format;
63 }
64
65 Structure* IntlDateTimeFormat::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
66 {
67     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
68 }
69
70 IntlDateTimeFormat::IntlDateTimeFormat(VM& vm, Structure* structure)
71     : JSDestructibleObject(vm, structure)
72 {
73 }
74
75 void IntlDateTimeFormat::finishCreation(VM& vm)
76 {
77     Base::finishCreation(vm);
78     ASSERT(inherits(info()));
79 }
80
81 void IntlDateTimeFormat::destroy(JSCell* cell)
82 {
83     static_cast<IntlDateTimeFormat*>(cell)->IntlDateTimeFormat::~IntlDateTimeFormat();
84 }
85
86 void IntlDateTimeFormat::visitChildren(JSCell* cell, SlotVisitor& visitor)
87 {
88     IntlDateTimeFormat* thisObject = jsCast<IntlDateTimeFormat*>(cell);
89     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
90
91     Base::visitChildren(thisObject, visitor);
92
93     visitor.append(thisObject->m_boundFormat);
94 }
95
96 void IntlDateTimeFormat::setBoundFormat(VM& vm, JSBoundFunction* format)
97 {
98     m_boundFormat.set(vm, this, format);
99 }
100
101 static String defaultTimeZone()
102 {
103     // 6.4.3 DefaultTimeZone () (ECMA-402 2.0)
104     // The DefaultTimeZone abstract operation returns a String value representing the valid (6.4.1) and canonicalized (6.4.2) time zone name for the host environment’s current time zone.
105
106     UErrorCode status = U_ZERO_ERROR;
107     Vector<UChar, 32> buffer(32);
108     auto bufferLength = ucal_getDefaultTimeZone(buffer.data(), buffer.size(), &status);
109     if (status == U_BUFFER_OVERFLOW_ERROR) {
110         status = U_ZERO_ERROR;
111         buffer.grow(bufferLength);
112         ucal_getDefaultTimeZone(buffer.data(), bufferLength, &status);
113     }
114     if (U_SUCCESS(status)) {
115         status = U_ZERO_ERROR;
116         Vector<UChar, 32> canonicalBuffer(32);
117         auto canonicalLength = ucal_getCanonicalTimeZoneID(buffer.data(), bufferLength, canonicalBuffer.data(), canonicalBuffer.size(), nullptr, &status);
118         if (status == U_BUFFER_OVERFLOW_ERROR) {
119             status = U_ZERO_ERROR;
120             canonicalBuffer.grow(canonicalLength);
121             ucal_getCanonicalTimeZoneID(buffer.data(), bufferLength, canonicalBuffer.data(), canonicalLength, nullptr, &status);
122         }
123         if (U_SUCCESS(status))
124             return String(canonicalBuffer.data(), canonicalLength);
125     }
126
127     return ASCIILiteral("UTC");
128 }
129
130 static String canonicalizeTimeZoneName(const String& timeZoneName)
131 {
132     // 6.4.1 IsValidTimeZoneName (timeZone)
133     // The abstract operation returns true if timeZone, converted to upper case as described in 6.1, is equal to one of the Zone or Link names of the IANA Time Zone Database, converted to upper case as described in 6.1. It returns false otherwise.
134     UErrorCode status = U_ZERO_ERROR;
135     UEnumeration* timeZones = ucal_openTimeZones(&status);
136     ASSERT(U_SUCCESS(status));
137
138     String canonical;
139     do {
140         status = U_ZERO_ERROR;
141         int32_t ianaTimeZoneLength;
142         // Time zone names are respresented as UChar[] in all related ICU apis.
143         const UChar* ianaTimeZone = uenum_unext(timeZones, &ianaTimeZoneLength, &status);
144         ASSERT(U_SUCCESS(status));
145
146         // End of enumeration.
147         if (!ianaTimeZone)
148             break;
149
150         StringView ianaTimeZoneView(ianaTimeZone, ianaTimeZoneLength);
151         if (!equalIgnoringASCIICase(timeZoneName, ianaTimeZoneView))
152             continue;
153
154         // Found a match, now canonicalize.
155         // 6.4.2 CanonicalizeTimeZoneName (timeZone) (ECMA-402 2.0)
156         // 1. Let ianaTimeZone be the Zone or Link name of the IANA Time Zone Database such that timeZone, converted to upper case as described in 6.1, is equal to ianaTimeZone, converted to upper case as described in 6.1.
157         // 2. If ianaTimeZone is a Link name, then let ianaTimeZone be the corresponding Zone name as specified in the “backward” file of the IANA Time Zone Database.
158
159         Vector<UChar, 32> buffer(ianaTimeZoneLength);
160         status = U_ZERO_ERROR;
161         auto canonicalLength = ucal_getCanonicalTimeZoneID(ianaTimeZone, ianaTimeZoneLength, buffer.data(), ianaTimeZoneLength, nullptr, &status);
162         if (status == U_BUFFER_OVERFLOW_ERROR) {
163             buffer.grow(canonicalLength);
164             status = U_ZERO_ERROR;
165             ucal_getCanonicalTimeZoneID(ianaTimeZone, ianaTimeZoneLength, buffer.data(), canonicalLength, nullptr, &status);
166         }
167         ASSERT(U_SUCCESS(status));
168         canonical = String(buffer.data(), canonicalLength);
169     } while (canonical.isNull());
170     uenum_close(timeZones);
171
172     // 3. If ianaTimeZone is "Etc/UTC" or "Etc/GMT", then return "UTC".
173     if (canonical == "Etc/UTC" || canonical == "Etc/GMT")
174         canonical = ASCIILiteral("UTC");
175
176     // 4. Return ianaTimeZone.
177     return canonical;
178 }
179
180 static Vector<String> localeData(const String& locale, size_t keyIndex)
181 {
182     Vector<String> keyLocaleData;
183     switch (keyIndex) {
184     case indexOfExtensionKeyCa: {
185         UErrorCode status = U_ZERO_ERROR;
186         UEnumeration* calendars = ucal_getKeywordValuesForLocale("calendar", locale.utf8().data(), false, &status);
187         ASSERT(U_SUCCESS(status));
188
189         int32_t nameLength;
190         while (const char* availableName = uenum_next(calendars, &nameLength, &status)) {
191             ASSERT(U_SUCCESS(status));
192             String calendar = String(availableName, nameLength);
193             keyLocaleData.append(calendar);
194             // Ensure aliases used in language tag are allowed.
195             if (calendar == "gregorian")
196                 keyLocaleData.append(ASCIILiteral("gregory"));
197             else if (calendar == "islamic-civil")
198                 keyLocaleData.append(ASCIILiteral("islamicc"));
199             else if (calendar == "ethiopic-amete-alem")
200                 keyLocaleData.append(ASCIILiteral("ethioaa"));
201         }
202         uenum_close(calendars);
203         break;
204     }
205     case indexOfExtensionKeyNu:
206         keyLocaleData = numberingSystemsForLocale(locale);
207         break;
208     default:
209         ASSERT_NOT_REACHED();
210     }
211     return keyLocaleData;
212 }
213
214 static JSObject* toDateTimeOptionsAnyDate(ExecState& exec, JSValue originalOptions)
215 {
216     // 12.1.1 ToDateTimeOptions abstract operation (ECMA-402 2.0)
217     VM& vm = exec.vm();
218     auto scope = DECLARE_THROW_SCOPE(vm);
219
220     // 1. If options is undefined, then let options be null, else let options be ToObject(options).
221     // 2. ReturnIfAbrupt(options).
222     // 3. Let options be ObjectCreate(options).
223     JSObject* options;
224     if (originalOptions.isUndefined())
225         options = constructEmptyObject(&exec, exec.lexicalGlobalObject()->nullPrototypeObjectStructure());
226     else {
227         JSObject* originalToObject = originalOptions.toObject(&exec);
228         RETURN_IF_EXCEPTION(scope, nullptr);
229         options = constructEmptyObject(&exec, originalToObject);
230     }
231
232     // 4. Let needDefaults be true.
233     bool needDefaults = true;
234
235     // 5. If required is "date" or "any",
236     // Always "any".
237
238     // a. For each of the property names "weekday", "year", "month", "day":
239     // i. Let prop be the property name.
240     // ii. Let value be Get(options, prop).
241     // iii. ReturnIfAbrupt(value).
242     // iv. If value is not undefined, then let needDefaults be false.
243     JSValue weekday = options->get(&exec, vm.propertyNames->weekday);
244     RETURN_IF_EXCEPTION(scope, nullptr);
245     if (!weekday.isUndefined())
246         needDefaults = false;
247
248     JSValue year = options->get(&exec, vm.propertyNames->year);
249     RETURN_IF_EXCEPTION(scope, nullptr);
250     if (!year.isUndefined())
251         needDefaults = false;
252
253     JSValue month = options->get(&exec, vm.propertyNames->month);
254     RETURN_IF_EXCEPTION(scope, nullptr);
255     if (!month.isUndefined())
256         needDefaults = false;
257
258     JSValue day = options->get(&exec, vm.propertyNames->day);
259     RETURN_IF_EXCEPTION(scope, nullptr);
260     if (!day.isUndefined())
261         needDefaults = false;
262
263     // 6. If required is "time" or "any",
264     // Always "any".
265
266     // a. For each of the property names "hour", "minute", "second":
267     // i. Let prop be the property name.
268     // ii. Let value be Get(options, prop).
269     // iii. ReturnIfAbrupt(value).
270     // iv. If value is not undefined, then let needDefaults be false.
271     JSValue hour = options->get(&exec, vm.propertyNames->hour);
272     RETURN_IF_EXCEPTION(scope, nullptr);
273     if (!hour.isUndefined())
274         needDefaults = false;
275
276     JSValue minute = options->get(&exec, vm.propertyNames->minute);
277     RETURN_IF_EXCEPTION(scope, nullptr);
278     if (!minute.isUndefined())
279         needDefaults = false;
280
281     JSValue second = options->get(&exec, vm.propertyNames->second);
282     RETURN_IF_EXCEPTION(scope, nullptr);
283     if (!second.isUndefined())
284         needDefaults = false;
285
286     // 7. If needDefaults is true and defaults is either "date" or "all", then
287     // Defaults is always "date".
288     if (needDefaults) {
289         // a. For each of the property names "year", "month", "day":
290         // i. Let status be CreateDatePropertyOrThrow(options, prop, "numeric").
291         // ii. ReturnIfAbrupt(status).
292         JSString* numeric = jsNontrivialString(&exec, ASCIILiteral("numeric"));
293
294         options->putDirect(vm, vm.propertyNames->year, numeric);
295         RETURN_IF_EXCEPTION(scope, nullptr);
296
297         options->putDirect(vm, vm.propertyNames->month, numeric);
298         RETURN_IF_EXCEPTION(scope, nullptr);
299
300         options->putDirect(vm, vm.propertyNames->day, numeric);
301         RETURN_IF_EXCEPTION(scope, nullptr);
302     }
303
304     // 8. If needDefaults is true and defaults is either "time" or "all", then
305     // Defaults is always "date". Ignore this branch.
306
307     // 9. Return options.
308     return options;
309 }
310
311 void IntlDateTimeFormat::setFormatsFromPattern(const StringView& pattern)
312 {
313     // Get all symbols from the pattern, and set format fields accordingly.
314     // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
315     unsigned length = pattern.length();
316     for (unsigned i = 0; i < length; ++i) {
317         UChar currentCharacter = pattern[i];
318         if (!isASCIIAlpha(currentCharacter))
319             continue;
320
321         unsigned count = 1;
322         while (i + 1 < length && pattern[i + 1] == currentCharacter) {
323             ++count;
324             ++i;
325         }
326
327         if (currentCharacter == 'h' || currentCharacter == 'K')
328             m_hour12 = true;
329         else if (currentCharacter == 'H' || currentCharacter == 'k')
330             m_hour12 = false;
331
332         switch (currentCharacter) {
333         case 'G':
334             if (count <= 3)
335                 m_era = Era::Short;
336             else if (count == 4)
337                 m_era = Era::Long;
338             else if (count == 5)
339                 m_era = Era::Narrow;
340             break;
341         case 'y':
342             if (count == 1)
343                 m_year = Year::Numeric;
344             else if (count == 2)
345                 m_year = Year::TwoDigit;
346             break;
347         case 'M':
348         case 'L':
349             if (count == 1)
350                 m_month = Month::Numeric;
351             else if (count == 2)
352                 m_month = Month::TwoDigit;
353             else if (count == 3)
354                 m_month = Month::Short;
355             else if (count == 4)
356                 m_month = Month::Long;
357             else if (count == 5)
358                 m_month = Month::Narrow;
359             break;
360         case 'E':
361         case 'e':
362         case 'c':
363             if (count <= 3)
364                 m_weekday = Weekday::Short;
365             else if (count == 4)
366                 m_weekday = Weekday::Long;
367             else if (count == 5)
368                 m_weekday = Weekday::Narrow;
369             break;
370         case 'd':
371             if (count == 1)
372                 m_day = Day::Numeric;
373             else if (count == 2)
374                 m_day = Day::TwoDigit;
375             break;
376         case 'h':
377         case 'H':
378         case 'k':
379         case 'K':
380             if (count == 1)
381                 m_hour = Hour::Numeric;
382             else if (count == 2)
383                 m_hour = Hour::TwoDigit;
384             break;
385         case 'm':
386             if (count == 1)
387                 m_minute = Minute::Numeric;
388             else if (count == 2)
389                 m_minute = Minute::TwoDigit;
390             break;
391         case 's':
392             if (count == 1)
393                 m_second = Second::Numeric;
394             else if (count == 2)
395                 m_second = Second::TwoDigit;
396             break;
397         case 'z':
398         case 'v':
399         case 'V':
400             if (count == 1)
401                 m_timeZoneName = TimeZoneName::Short;
402             else if (count == 4)
403                 m_timeZoneName = TimeZoneName::Long;
404             break;
405         }
406     }
407 }
408
409 void IntlDateTimeFormat::initializeDateTimeFormat(ExecState& exec, JSValue locales, JSValue originalOptions)
410 {
411     VM& vm = exec.vm();
412     auto scope = DECLARE_THROW_SCOPE(vm);
413
414     // 12.1.1 InitializeDateTimeFormat (dateTimeFormat, locales, options) (ECMA-402 2.0)
415     // 1. If dateTimeFormat.[[initializedIntlObject]] is true, throw a TypeError exception.
416     // 2. Set dateTimeFormat.[[initializedIntlObject]] to true.
417
418     // 3. Let requestedLocales be CanonicalizeLocaleList(locales).
419     Vector<String> requestedLocales = canonicalizeLocaleList(exec, locales);
420     // 4. ReturnIfAbrupt(requestedLocales),
421     RETURN_IF_EXCEPTION(scope, void());
422
423     // 5. Let options be ToDateTimeOptions(options, "any", "date").
424     JSObject* options = toDateTimeOptionsAnyDate(exec, originalOptions);
425     // 6. ReturnIfAbrupt(options).
426     RETURN_IF_EXCEPTION(scope, void());
427
428     // 7. Let opt be a new Record.
429     HashMap<String, String> localeOpt;
430
431     // 8. Let matcher be GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
432     String localeMatcher = intlStringOption(exec, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit");
433     // 9. ReturnIfAbrupt(matcher).
434     RETURN_IF_EXCEPTION(scope, void());
435     // 10. Set opt.[[localeMatcher]] to matcher.
436     localeOpt.add(vm.propertyNames->localeMatcher.string(), localeMatcher);
437
438     // 11. Let localeData be the value of %DateTimeFormat%.[[localeData]].
439     // 12. Let r be ResolveLocale( %DateTimeFormat%.[[availableLocales]], requestedLocales, opt, %DateTimeFormat%.[[relevantExtensionKeys]], localeData).
440     const HashSet<String> availableLocales = exec.jsCallee()->globalObject()->intlDateTimeFormatAvailableLocales();
441     HashMap<String, String> resolved = resolveLocale(exec, availableLocales, requestedLocales, localeOpt, relevantExtensionKeys, WTF_ARRAY_LENGTH(relevantExtensionKeys), localeData);
442
443     // 13. Set dateTimeFormat.[[locale]] to the value of r.[[locale]].
444     m_locale = resolved.get(vm.propertyNames->locale.string());
445     if (m_locale.isEmpty()) {
446         throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat due to invalid locale"));
447         return;
448     }
449     // 14. Set dateTimeFormat.[[calendar]] to the value of r.[[ca]].
450     m_calendar = resolved.get(ASCIILiteral("ca"));
451     // Switch to preferred aliases.
452     if (m_calendar == "gregory")
453         m_calendar = ASCIILiteral("gregorian");
454     else if (m_calendar == "islamicc")
455         m_calendar = ASCIILiteral("islamic-civil");
456     else if (m_calendar == "ethioaa")
457         m_calendar = ASCIILiteral("ethiopic-amete-alem");
458     // 15. Set dateTimeFormat.[[numberingSystem]] to the value of r.[[nu]].
459     m_numberingSystem = resolved.get(ASCIILiteral("nu"));
460     // 16. Let dataLocale be the value of r.[[dataLocale]].
461     String dataLocale = resolved.get(ASCIILiteral("dataLocale"));
462
463     // 17. Let tz be Get(options, "timeZone").
464     JSValue tzValue = options->get(&exec, vm.propertyNames->timeZone);
465     // 18. ReturnIfAbrupt(tz).
466     RETURN_IF_EXCEPTION(scope, void());
467
468     // 19. If tz is not undefined, then
469     String tz;
470     if (!tzValue.isUndefined()) {
471         // a. Let tz be ToString(tz).
472         String originalTz = tzValue.toWTFString(&exec);
473         // b. ReturnIfAbrupt(tz).
474         RETURN_IF_EXCEPTION(scope, void());
475         // c. If the result of IsValidTimeZoneName(tz) is false, then i. Throw a RangeError exception.
476         // d. Let tz be CanonicalizeTimeZoneName(tz).
477         tz = canonicalizeTimeZoneName(originalTz);
478         if (tz.isNull()) {
479             throwRangeError(&exec, scope, String::format("invalid time zone: %s", originalTz.utf8().data()));
480             return;
481         }
482     } else {
483         // 20. Else,
484         // a. Let tz be DefaultTimeZone().
485         tz = defaultTimeZone();
486     }
487
488     // 21. Set dateTimeFormat.[[timeZone]] to tz.
489     m_timeZone = tz;
490
491     // 22. Let opt be a new Record.
492     // Rather than building a record, build the skeleton pattern.
493     StringBuilder skeletonBuilder;
494
495     // 23. For each row of Table 3, except the header row, do:
496     // a. Let prop be the name given in the Property column of the row.
497     // b. Let value be GetOption(options, prop, "string", «the strings given in the Values column of the row», undefined).
498     // c. ReturnIfAbrupt(value).
499     // d. Set opt.[[<prop>]] to value.
500     auto narrowShortLong = { "narrow", "short", "long" };
501     auto twoDigitNumeric = { "2-digit", "numeric" };
502     auto twoDigitNumericNarrowShortLong = { "2-digit", "numeric", "narrow", "short", "long" };
503     auto shortLong = { "short", "long" };
504
505     String weekday = intlStringOption(exec, options, vm.propertyNames->weekday, narrowShortLong, "weekday must be \"narrow\", \"short\", or \"long\"", nullptr);
506     RETURN_IF_EXCEPTION(scope, void());
507     if (!weekday.isNull()) {
508         if (weekday == "narrow")
509             skeletonBuilder.appendLiteral("EEEEE");
510         else if (weekday == "short")
511             skeletonBuilder.appendLiteral("EEE");
512         else if (weekday == "long")
513             skeletonBuilder.appendLiteral("EEEE");
514     }
515
516     String era = intlStringOption(exec, options, vm.propertyNames->era, narrowShortLong, "era must be \"narrow\", \"short\", or \"long\"", nullptr);
517     RETURN_IF_EXCEPTION(scope, void());
518     if (!era.isNull()) {
519         if (era == "narrow")
520             skeletonBuilder.appendLiteral("GGGGG");
521         else if (era == "short")
522             skeletonBuilder.appendLiteral("GGG");
523         else if (era == "long")
524             skeletonBuilder.appendLiteral("GGGG");
525     }
526
527     String year = intlStringOption(exec, options, vm.propertyNames->year, twoDigitNumeric, "year must be \"2-digit\" or \"numeric\"", nullptr);
528     RETURN_IF_EXCEPTION(scope, void());
529     if (!year.isNull()) {
530         if (year == "2-digit")
531             skeletonBuilder.appendLiteral("yy");
532         else if (year == "numeric")
533             skeletonBuilder.append('y');
534     }
535
536     String month = intlStringOption(exec, options, vm.propertyNames->month, twoDigitNumericNarrowShortLong, "month must be \"2-digit\", \"numeric\", \"narrow\", \"short\", or \"long\"", nullptr);
537     RETURN_IF_EXCEPTION(scope, void());
538     if (!month.isNull()) {
539         if (month == "2-digit")
540             skeletonBuilder.appendLiteral("MM");
541         else if (month == "numeric")
542             skeletonBuilder.append('M');
543         else if (month == "narrow")
544             skeletonBuilder.appendLiteral("MMMMM");
545         else if (month == "short")
546             skeletonBuilder.appendLiteral("MMM");
547         else if (month == "long")
548             skeletonBuilder.appendLiteral("MMMM");
549     }
550
551     String day = intlStringOption(exec, options, vm.propertyNames->day, twoDigitNumeric, "day must be \"2-digit\" or \"numeric\"", nullptr);
552     RETURN_IF_EXCEPTION(scope, void());
553     if (!day.isNull()) {
554         if (day == "2-digit")
555             skeletonBuilder.appendLiteral("dd");
556         else if (day == "numeric")
557             skeletonBuilder.append('d');
558     }
559
560     String hour = intlStringOption(exec, options, vm.propertyNames->hour, twoDigitNumeric, "hour must be \"2-digit\" or \"numeric\"", nullptr);
561     RETURN_IF_EXCEPTION(scope, void());
562
563     // We need hour12 to make the hour skeleton pattern decision, so do this early.
564     // 32. Let hr12 be GetOption(options, "hour12", "boolean", undefined, undefined).
565     bool isHour12Undefined;
566     bool hr12 = intlBooleanOption(exec, options, vm.propertyNames->hour12, isHour12Undefined);
567     // 33. ReturnIfAbrupt(hr12).
568     RETURN_IF_EXCEPTION(scope, void());
569
570     if (!hour.isNull()) {
571         if (isHour12Undefined) {
572             if (hour == "2-digit")
573                 skeletonBuilder.appendLiteral("jj");
574             else if (hour == "numeric")
575                 skeletonBuilder.append('j');
576         } else if (hr12) {
577             if (hour == "2-digit")
578                 skeletonBuilder.appendLiteral("hh");
579             else if (hour == "numeric")
580                 skeletonBuilder.append('h');
581         } else {
582             if (hour == "2-digit")
583                 skeletonBuilder.appendLiteral("HH");
584             else if (hour == "numeric")
585                 skeletonBuilder.append('H');
586         }
587     }
588
589     String minute = intlStringOption(exec, options, vm.propertyNames->minute, twoDigitNumeric, "minute must be \"2-digit\" or \"numeric\"", nullptr);
590     RETURN_IF_EXCEPTION(scope, void());
591     if (!minute.isNull()) {
592         if (minute == "2-digit")
593             skeletonBuilder.appendLiteral("mm");
594         else if (minute == "numeric")
595             skeletonBuilder.append('m');
596     }
597
598     String second = intlStringOption(exec, options, vm.propertyNames->second, twoDigitNumeric, "second must be \"2-digit\" or \"numeric\"", nullptr);
599     RETURN_IF_EXCEPTION(scope, void());
600     if (!second.isNull()) {
601         if (second == "2-digit")
602             skeletonBuilder.appendLiteral("ss");
603         else if (second == "numeric")
604             skeletonBuilder.append('s');
605     }
606
607     String timeZoneName = intlStringOption(exec, options, vm.propertyNames->timeZoneName, shortLong, "timeZoneName must be \"short\" or \"long\"", nullptr);
608     RETURN_IF_EXCEPTION(scope, void());
609     if (!timeZoneName.isNull()) {
610         if (timeZoneName == "short")
611             skeletonBuilder.append('z');
612         else if (timeZoneName == "long")
613             skeletonBuilder.appendLiteral("zzzz");
614     }
615
616     // 24. Let dataLocaleData be Get(localeData, dataLocale).
617     // 25. Let formats be Get(dataLocaleData, "formats").
618     // 26. Let matcher be GetOption(options, "formatMatcher", "string", «"basic", "best fit"», "best fit").
619     intlStringOption(exec, options, vm.propertyNames->formatMatcher, { "basic", "best fit" }, "formatMatcher must be either \"basic\" or \"best fit\"", "best fit");
620     // 27. ReturnIfAbrupt(matcher).
621     RETURN_IF_EXCEPTION(scope, void());
622
623     // Always use ICU date format generator, rather than our own pattern list and matcher.
624     // Covers steps 28-36.
625     UErrorCode status = U_ZERO_ERROR;
626     UDateTimePatternGenerator* generator = udatpg_open(dataLocale.utf8().data(), &status);
627     if (U_FAILURE(status)) {
628         throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat"));
629         return;
630     }
631
632     String skeleton = skeletonBuilder.toString();
633     StringView skeletonView(skeleton);
634     Vector<UChar, 32> patternBuffer(32);
635     status = U_ZERO_ERROR;
636     auto patternLength = udatpg_getBestPattern(generator, skeletonView.upconvertedCharacters(), skeletonView.length(), patternBuffer.data(), patternBuffer.size(), &status);
637     if (status == U_BUFFER_OVERFLOW_ERROR) {
638         status = U_ZERO_ERROR;
639         patternBuffer.grow(patternLength);
640         udatpg_getBestPattern(generator, skeletonView.upconvertedCharacters(), skeletonView.length(), patternBuffer.data(), patternLength, &status);
641     }
642     udatpg_close(generator);
643     if (U_FAILURE(status)) {
644         throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat"));
645         return;
646     }
647
648     StringView pattern(patternBuffer.data(), patternLength);
649     setFormatsFromPattern(pattern);
650
651     status = U_ZERO_ERROR;
652     StringView timeZoneView(m_timeZone);
653     m_dateFormat = std::unique_ptr<UDateFormat, UDateFormatDeleter>(udat_open(UDAT_PATTERN, UDAT_PATTERN, m_locale.utf8().data(), timeZoneView.upconvertedCharacters(), timeZoneView.length(), pattern.upconvertedCharacters(), pattern.length(), &status));
654     if (U_FAILURE(status)) {
655         throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat"));
656         return;
657     }
658
659     // 37. Set dateTimeFormat.[[boundFormat]] to undefined.
660     // Already undefined.
661
662     // 38. Set dateTimeFormat.[[initializedDateTimeFormat]] to true.
663     m_initializedDateTimeFormat = true;
664
665     // 39. Return dateTimeFormat.
666 }
667
668 const char* IntlDateTimeFormat::weekdayString(Weekday weekday)
669 {
670     switch (weekday) {
671     case Weekday::Narrow:
672         return "narrow";
673     case Weekday::Short:
674         return "short";
675     case Weekday::Long:
676         return "long";
677     case Weekday::None:
678         ASSERT_NOT_REACHED();
679         return nullptr;
680     }
681     ASSERT_NOT_REACHED();
682     return nullptr;
683 }
684
685 const char* IntlDateTimeFormat::eraString(Era era)
686 {
687     switch (era) {
688     case Era::Narrow:
689         return "narrow";
690     case Era::Short:
691         return "short";
692     case Era::Long:
693         return "long";
694     case Era::None:
695         ASSERT_NOT_REACHED();
696         return nullptr;
697     }
698     ASSERT_NOT_REACHED();
699     return nullptr;
700 }
701
702 const char* IntlDateTimeFormat::yearString(Year year)
703 {
704     switch (year) {
705     case Year::TwoDigit:
706         return "2-digit";
707     case Year::Numeric:
708         return "numeric";
709     case Year::None:
710         ASSERT_NOT_REACHED();
711         return nullptr;
712     }
713     ASSERT_NOT_REACHED();
714     return nullptr;
715 }
716
717 const char* IntlDateTimeFormat::monthString(Month month)
718 {
719     switch (month) {
720     case Month::TwoDigit:
721         return "2-digit";
722     case Month::Numeric:
723         return "numeric";
724     case Month::Narrow:
725         return "narrow";
726     case Month::Short:
727         return "short";
728     case Month::Long:
729         return "long";
730     case Month::None:
731         ASSERT_NOT_REACHED();
732         return nullptr;
733     }
734     ASSERT_NOT_REACHED();
735     return nullptr;
736 }
737
738 const char* IntlDateTimeFormat::dayString(Day day)
739 {
740     switch (day) {
741     case Day::TwoDigit:
742         return "2-digit";
743     case Day::Numeric:
744         return "numeric";
745     case Day::None:
746         ASSERT_NOT_REACHED();
747         return nullptr;
748     }
749     ASSERT_NOT_REACHED();
750     return nullptr;
751 }
752
753 const char* IntlDateTimeFormat::hourString(Hour hour)
754 {
755     switch (hour) {
756     case Hour::TwoDigit:
757         return "2-digit";
758     case Hour::Numeric:
759         return "numeric";
760     case Hour::None:
761         ASSERT_NOT_REACHED();
762         return nullptr;
763     }
764     ASSERT_NOT_REACHED();
765     return nullptr;
766 }
767
768 const char* IntlDateTimeFormat::minuteString(Minute minute)
769 {
770     switch (minute) {
771     case Minute::TwoDigit:
772         return "2-digit";
773     case Minute::Numeric:
774         return "numeric";
775     case Minute::None:
776         ASSERT_NOT_REACHED();
777         return nullptr;
778     }
779     ASSERT_NOT_REACHED();
780     return nullptr;
781 }
782
783 const char* IntlDateTimeFormat::secondString(Second second)
784 {
785     switch (second) {
786     case Second::TwoDigit:
787         return "2-digit";
788     case Second::Numeric:
789         return "numeric";
790     case Second::None:
791         ASSERT_NOT_REACHED();
792         return nullptr;
793     }
794     ASSERT_NOT_REACHED();
795     return nullptr;
796 }
797
798 const char* IntlDateTimeFormat::timeZoneNameString(TimeZoneName timeZoneName)
799 {
800     switch (timeZoneName) {
801     case TimeZoneName::Short:
802         return "short";
803     case TimeZoneName::Long:
804         return "long";
805     case TimeZoneName::None:
806         ASSERT_NOT_REACHED();
807         return nullptr;
808     }
809     ASSERT_NOT_REACHED();
810     return nullptr;
811 }
812
813 JSObject* IntlDateTimeFormat::resolvedOptions(ExecState& exec)
814 {
815     VM& vm = exec.vm();
816     auto scope = DECLARE_THROW_SCOPE(vm);
817
818     // 12.3.5 Intl.DateTimeFormat.prototype.resolvedOptions() (ECMA-402 2.0)
819     // The function returns a new object whose properties and attributes are set as if constructed by an object literal assigning to each of the following properties the value of the corresponding internal slot of this DateTimeFormat object (see 12.4): locale, calendar, numberingSystem, timeZone, hour12, weekday, era, year, month, day, hour, minute, second, and timeZoneName. Properties whose corresponding internal slots are not present are not assigned.
820     // Note: In this version of the ECMAScript 2015 Internationalization API, the timeZone property will be the name of the default time zone if no timeZone property was provided in the options object provided to the Intl.DateTimeFormat constructor. The previous version left the timeZone property undefined in this case.
821     if (!m_initializedDateTimeFormat) {
822         initializeDateTimeFormat(exec, jsUndefined(), jsUndefined());
823         ASSERT_UNUSED(scope, !scope.exception());
824     }
825
826     JSObject* options = constructEmptyObject(&exec);
827     options->putDirect(vm, vm.propertyNames->locale, jsNontrivialString(&exec, m_locale));
828     options->putDirect(vm, vm.propertyNames->calendar, jsNontrivialString(&exec, m_calendar));
829     options->putDirect(vm, vm.propertyNames->numberingSystem, jsNontrivialString(&exec, m_numberingSystem));
830     options->putDirect(vm, vm.propertyNames->timeZone, jsNontrivialString(&exec, m_timeZone));
831
832     if (m_weekday != Weekday::None)
833         options->putDirect(vm, vm.propertyNames->weekday, jsNontrivialString(&exec, ASCIILiteral(weekdayString(m_weekday))));
834
835     if (m_era != Era::None)
836         options->putDirect(vm, vm.propertyNames->era, jsNontrivialString(&exec, ASCIILiteral(eraString(m_era))));
837
838     if (m_year != Year::None)
839         options->putDirect(vm, vm.propertyNames->year, jsNontrivialString(&exec, ASCIILiteral(yearString(m_year))));
840
841     if (m_month != Month::None)
842         options->putDirect(vm, vm.propertyNames->month, jsNontrivialString(&exec, ASCIILiteral(monthString(m_month))));
843
844     if (m_day != Day::None)
845         options->putDirect(vm, vm.propertyNames->day, jsNontrivialString(&exec, ASCIILiteral(dayString(m_day))));
846
847     if (m_hour != Hour::None) {
848         options->putDirect(vm, vm.propertyNames->hour, jsNontrivialString(&exec, ASCIILiteral(hourString(m_hour))));
849         options->putDirect(vm, vm.propertyNames->hour12, jsBoolean(m_hour12));
850     }
851
852     if (m_minute != Minute::None)
853         options->putDirect(vm, vm.propertyNames->minute, jsNontrivialString(&exec, ASCIILiteral(minuteString(m_minute))));
854
855     if (m_second != Second::None)
856         options->putDirect(vm, vm.propertyNames->second, jsNontrivialString(&exec, ASCIILiteral(secondString(m_second))));
857
858     if (m_timeZoneName != TimeZoneName::None)
859         options->putDirect(vm, vm.propertyNames->timeZoneName, jsNontrivialString(&exec, ASCIILiteral(timeZoneNameString(m_timeZoneName))));
860
861     return options;
862 }
863
864 JSValue IntlDateTimeFormat::format(ExecState& exec, double value)
865 {
866     VM& vm = exec.vm();
867     auto scope = DECLARE_THROW_SCOPE(vm);
868
869     // 12.3.4 FormatDateTime abstract operation (ECMA-402 2.0)
870     if (!m_initializedDateTimeFormat) {
871         initializeDateTimeFormat(exec, jsUndefined(), jsUndefined());
872         ASSERT(!scope.exception());
873     }
874
875     // 1. If x is not a finite Number, then throw a RangeError exception.
876     if (!std::isfinite(value))
877         return throwRangeError(&exec, scope, ASCIILiteral("date value is not finite in DateTimeFormat format()"));
878
879     // Delegate remaining steps to ICU.
880     UErrorCode status = U_ZERO_ERROR;
881     Vector<UChar, 32> result(32);
882     auto resultLength = udat_format(m_dateFormat.get(), value, result.data(), result.size(), nullptr, &status);
883     if (status == U_BUFFER_OVERFLOW_ERROR) {
884         status = U_ZERO_ERROR;
885         result.grow(resultLength);
886         udat_format(m_dateFormat.get(), value, result.data(), resultLength, nullptr, &status);
887     }
888     if (U_FAILURE(status))
889         return throwTypeError(&exec, scope, ASCIILiteral("failed to format date value"));
890
891     return jsString(&exec, String(result.data(), resultLength));
892 }
893
894 } // namespace JSC
895
896 #endif // ENABLE(INTL)