Unreviewed, rolling out r234489.
[WebKit-https.git] / Source / JavaScriptCore / runtime / IntlDateTimeFormat.cpp
1 /*
2  * Copyright (C) 2015 Andy VanWagoner (andy@vanwagoner.family)
3  * Copyright (C) 2016-2017 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 #if JSC_ICU_HAS_UFIELDPOSITER
45 #include <unicode/ufieldpositer.h>
46 #endif
47
48 namespace JSC {
49
50 static const double minECMAScriptTime = -8.64E15;
51
52 const ClassInfo IntlDateTimeFormat::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlDateTimeFormat) };
53
54 namespace IntlDTFInternal {
55 static const char* const relevantExtensionKeys[3] = { "ca", "nu", "hc" };
56 }
57
58 static const size_t indexOfExtensionKeyCa = 0;
59 static const size_t indexOfExtensionKeyNu = 1;
60 static const size_t indexOfExtensionKeyHc = 2;
61
62 void IntlDateTimeFormat::UDateFormatDeleter::operator()(UDateFormat* dateFormat) const
63 {
64     if (dateFormat)
65         udat_close(dateFormat);
66 }
67
68 #if JSC_ICU_HAS_UFIELDPOSITER
69 void IntlDateTimeFormat::UFieldPositionIteratorDeleter::operator()(UFieldPositionIterator* iterator) const
70 {
71     if (iterator)
72         ufieldpositer_close(iterator);
73 }
74 #endif
75
76 IntlDateTimeFormat* IntlDateTimeFormat::create(VM& vm, Structure* structure)
77 {
78     IntlDateTimeFormat* format = new (NotNull, allocateCell<IntlDateTimeFormat>(vm.heap)) IntlDateTimeFormat(vm, structure);
79     format->finishCreation(vm);
80     return format;
81 }
82
83 Structure* IntlDateTimeFormat::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
84 {
85     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
86 }
87
88 IntlDateTimeFormat::IntlDateTimeFormat(VM& vm, Structure* structure)
89     : JSDestructibleObject(vm, structure)
90 {
91 }
92
93 void IntlDateTimeFormat::finishCreation(VM& vm)
94 {
95     Base::finishCreation(vm);
96     ASSERT(inherits(vm, info()));
97 }
98
99 void IntlDateTimeFormat::destroy(JSCell* cell)
100 {
101     static_cast<IntlDateTimeFormat*>(cell)->IntlDateTimeFormat::~IntlDateTimeFormat();
102 }
103
104 void IntlDateTimeFormat::visitChildren(JSCell* cell, SlotVisitor& visitor)
105 {
106     IntlDateTimeFormat* thisObject = jsCast<IntlDateTimeFormat*>(cell);
107     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
108
109     Base::visitChildren(thisObject, visitor);
110
111     visitor.append(thisObject->m_boundFormat);
112 }
113
114 void IntlDateTimeFormat::setBoundFormat(VM& vm, JSBoundFunction* format)
115 {
116     m_boundFormat.set(vm, this, format);
117 }
118
119 static String defaultTimeZone()
120 {
121     // 6.4.3 DefaultTimeZone () (ECMA-402 2.0)
122     // 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.
123
124     UErrorCode status = U_ZERO_ERROR;
125     Vector<UChar, 32> buffer(32);
126     auto bufferLength = ucal_getDefaultTimeZone(buffer.data(), buffer.size(), &status);
127     if (status == U_BUFFER_OVERFLOW_ERROR) {
128         status = U_ZERO_ERROR;
129         buffer.grow(bufferLength);
130         ucal_getDefaultTimeZone(buffer.data(), bufferLength, &status);
131     }
132     if (U_SUCCESS(status)) {
133         status = U_ZERO_ERROR;
134         Vector<UChar, 32> canonicalBuffer(32);
135         auto canonicalLength = ucal_getCanonicalTimeZoneID(buffer.data(), bufferLength, canonicalBuffer.data(), canonicalBuffer.size(), nullptr, &status);
136         if (status == U_BUFFER_OVERFLOW_ERROR) {
137             status = U_ZERO_ERROR;
138             canonicalBuffer.grow(canonicalLength);
139             ucal_getCanonicalTimeZoneID(buffer.data(), bufferLength, canonicalBuffer.data(), canonicalLength, nullptr, &status);
140         }
141         if (U_SUCCESS(status))
142             return String(canonicalBuffer.data(), canonicalLength);
143     }
144
145     return "UTC"_s;
146 }
147
148 static String canonicalizeTimeZoneName(const String& timeZoneName)
149 {
150     // 6.4.1 IsValidTimeZoneName (timeZone)
151     // 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.
152     UErrorCode status = U_ZERO_ERROR;
153     UEnumeration* timeZones = ucal_openTimeZones(&status);
154     ASSERT(U_SUCCESS(status));
155
156     String canonical;
157     do {
158         status = U_ZERO_ERROR;
159         int32_t ianaTimeZoneLength;
160         // Time zone names are respresented as UChar[] in all related ICU apis.
161         const UChar* ianaTimeZone = uenum_unext(timeZones, &ianaTimeZoneLength, &status);
162         ASSERT(U_SUCCESS(status));
163
164         // End of enumeration.
165         if (!ianaTimeZone)
166             break;
167
168         StringView ianaTimeZoneView(ianaTimeZone, ianaTimeZoneLength);
169         if (!equalIgnoringASCIICase(timeZoneName, ianaTimeZoneView))
170             continue;
171
172         // Found a match, now canonicalize.
173         // 6.4.2 CanonicalizeTimeZoneName (timeZone) (ECMA-402 2.0)
174         // 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.
175         // 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.
176
177         Vector<UChar, 32> buffer(ianaTimeZoneLength);
178         status = U_ZERO_ERROR;
179         auto canonicalLength = ucal_getCanonicalTimeZoneID(ianaTimeZone, ianaTimeZoneLength, buffer.data(), ianaTimeZoneLength, nullptr, &status);
180         if (status == U_BUFFER_OVERFLOW_ERROR) {
181             buffer.grow(canonicalLength);
182             status = U_ZERO_ERROR;
183             ucal_getCanonicalTimeZoneID(ianaTimeZone, ianaTimeZoneLength, buffer.data(), canonicalLength, nullptr, &status);
184         }
185         ASSERT(U_SUCCESS(status));
186         canonical = String(buffer.data(), canonicalLength);
187     } while (canonical.isNull());
188     uenum_close(timeZones);
189
190     // 3. If ianaTimeZone is "Etc/UTC" or "Etc/GMT", then return "UTC".
191     if (canonical == "Etc/UTC" || canonical == "Etc/GMT")
192         canonical = "UTC"_s;
193
194     // 4. Return ianaTimeZone.
195     return canonical;
196 }
197
198 namespace IntlDTFInternal {
199 static Vector<String> localeData(const String& locale, size_t keyIndex)
200 {
201     Vector<String> keyLocaleData;
202     switch (keyIndex) {
203     case indexOfExtensionKeyCa: {
204         UErrorCode status = U_ZERO_ERROR;
205         UEnumeration* calendars = ucal_getKeywordValuesForLocale("calendar", locale.utf8().data(), false, &status);
206         ASSERT(U_SUCCESS(status));
207
208         int32_t nameLength;
209         while (const char* availableName = uenum_next(calendars, &nameLength, &status)) {
210             ASSERT(U_SUCCESS(status));
211             String calendar = String(availableName, nameLength);
212             keyLocaleData.append(calendar);
213             // Ensure aliases used in language tag are allowed.
214             if (calendar == "gregorian")
215                 keyLocaleData.append("gregory"_s);
216             else if (calendar == "islamic-civil")
217                 keyLocaleData.append("islamicc"_s);
218             else if (calendar == "ethiopic-amete-alem")
219                 keyLocaleData.append("ethioaa"_s);
220         }
221         uenum_close(calendars);
222         break;
223     }
224     case indexOfExtensionKeyNu:
225         keyLocaleData = numberingSystemsForLocale(locale);
226         break;
227     case indexOfExtensionKeyHc:
228         // Null default so we know to use 'j' in pattern.
229         keyLocaleData.append(String());
230         keyLocaleData.append("h11"_s);
231         keyLocaleData.append("h12"_s);
232         keyLocaleData.append("h23"_s);
233         keyLocaleData.append("h24"_s);
234         break;
235     default:
236         ASSERT_NOT_REACHED();
237     }
238     return keyLocaleData;
239 }
240
241 static JSObject* toDateTimeOptionsAnyDate(ExecState& exec, JSValue originalOptions)
242 {
243     // 12.1.1 ToDateTimeOptions abstract operation (ECMA-402 2.0)
244     VM& vm = exec.vm();
245     auto scope = DECLARE_THROW_SCOPE(vm);
246
247     // 1. If options is undefined, then let options be null, else let options be ToObject(options).
248     // 2. ReturnIfAbrupt(options).
249     // 3. Let options be ObjectCreate(options).
250     JSObject* options;
251     if (originalOptions.isUndefined())
252         options = constructEmptyObject(&exec, exec.lexicalGlobalObject()->nullPrototypeObjectStructure());
253     else {
254         JSObject* originalToObject = originalOptions.toObject(&exec);
255         RETURN_IF_EXCEPTION(scope, nullptr);
256         options = constructEmptyObject(&exec, originalToObject);
257     }
258
259     // 4. Let needDefaults be true.
260     bool needDefaults = true;
261
262     // 5. If required is "date" or "any",
263     // Always "any".
264
265     // a. For each of the property names "weekday", "year", "month", "day":
266     // i. Let prop be the property name.
267     // ii. Let value be Get(options, prop).
268     // iii. ReturnIfAbrupt(value).
269     // iv. If value is not undefined, then let needDefaults be false.
270     JSValue weekday = options->get(&exec, vm.propertyNames->weekday);
271     RETURN_IF_EXCEPTION(scope, nullptr);
272     if (!weekday.isUndefined())
273         needDefaults = false;
274
275     JSValue year = options->get(&exec, vm.propertyNames->year);
276     RETURN_IF_EXCEPTION(scope, nullptr);
277     if (!year.isUndefined())
278         needDefaults = false;
279
280     JSValue month = options->get(&exec, vm.propertyNames->month);
281     RETURN_IF_EXCEPTION(scope, nullptr);
282     if (!month.isUndefined())
283         needDefaults = false;
284
285     JSValue day = options->get(&exec, vm.propertyNames->day);
286     RETURN_IF_EXCEPTION(scope, nullptr);
287     if (!day.isUndefined())
288         needDefaults = false;
289
290     // 6. If required is "time" or "any",
291     // Always "any".
292
293     // a. For each of the property names "hour", "minute", "second":
294     // i. Let prop be the property name.
295     // ii. Let value be Get(options, prop).
296     // iii. ReturnIfAbrupt(value).
297     // iv. If value is not undefined, then let needDefaults be false.
298     JSValue hour = options->get(&exec, vm.propertyNames->hour);
299     RETURN_IF_EXCEPTION(scope, nullptr);
300     if (!hour.isUndefined())
301         needDefaults = false;
302
303     JSValue minute = options->get(&exec, vm.propertyNames->minute);
304     RETURN_IF_EXCEPTION(scope, nullptr);
305     if (!minute.isUndefined())
306         needDefaults = false;
307
308     JSValue second = options->get(&exec, vm.propertyNames->second);
309     RETURN_IF_EXCEPTION(scope, nullptr);
310     if (!second.isUndefined())
311         needDefaults = false;
312
313     // 7. If needDefaults is true and defaults is either "date" or "all", then
314     // Defaults is always "date".
315     if (needDefaults) {
316         // a. For each of the property names "year", "month", "day":
317         // i. Let status be CreateDatePropertyOrThrow(options, prop, "numeric").
318         // ii. ReturnIfAbrupt(status).
319         JSString* numeric = jsNontrivialString(&exec, "numeric"_s);
320
321         options->putDirect(vm, vm.propertyNames->year, numeric);
322         RETURN_IF_EXCEPTION(scope, nullptr);
323
324         options->putDirect(vm, vm.propertyNames->month, numeric);
325         RETURN_IF_EXCEPTION(scope, nullptr);
326
327         options->putDirect(vm, vm.propertyNames->day, numeric);
328         RETURN_IF_EXCEPTION(scope, nullptr);
329     }
330
331     // 8. If needDefaults is true and defaults is either "time" or "all", then
332     // Defaults is always "date". Ignore this branch.
333
334     // 9. Return options.
335     return options;
336 }
337 }
338
339 void IntlDateTimeFormat::setFormatsFromPattern(const StringView& pattern)
340 {
341     // Get all symbols from the pattern, and set format fields accordingly.
342     // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
343     unsigned length = pattern.length();
344     for (unsigned i = 0; i < length; ++i) {
345         UChar currentCharacter = pattern[i];
346         if (!isASCIIAlpha(currentCharacter))
347             continue;
348
349         unsigned count = 1;
350         while (i + 1 < length && pattern[i + 1] == currentCharacter) {
351             ++count;
352             ++i;
353         }
354
355         // If hourCycle was null, this sets it to the locale default.
356         if (m_hourCycle.isNull()) {
357             if (currentCharacter == 'h')
358                 m_hourCycle = "h12"_s;
359             else if (currentCharacter == 'H')
360                 m_hourCycle = "h23"_s;
361             else if (currentCharacter == 'k')
362                 m_hourCycle = "h24"_s;
363             else if (currentCharacter == 'K')
364                 m_hourCycle = "h11"_s;
365         }
366
367         switch (currentCharacter) {
368         case 'G':
369             if (count <= 3)
370                 m_era = Era::Short;
371             else if (count == 4)
372                 m_era = Era::Long;
373             else if (count == 5)
374                 m_era = Era::Narrow;
375             break;
376         case 'y':
377             if (count == 1)
378                 m_year = Year::Numeric;
379             else if (count == 2)
380                 m_year = Year::TwoDigit;
381             break;
382         case 'M':
383         case 'L':
384             if (count == 1)
385                 m_month = Month::Numeric;
386             else if (count == 2)
387                 m_month = Month::TwoDigit;
388             else if (count == 3)
389                 m_month = Month::Short;
390             else if (count == 4)
391                 m_month = Month::Long;
392             else if (count == 5)
393                 m_month = Month::Narrow;
394             break;
395         case 'E':
396         case 'e':
397         case 'c':
398             if (count <= 3)
399                 m_weekday = Weekday::Short;
400             else if (count == 4)
401                 m_weekday = Weekday::Long;
402             else if (count == 5)
403                 m_weekday = Weekday::Narrow;
404             break;
405         case 'd':
406             if (count == 1)
407                 m_day = Day::Numeric;
408             else if (count == 2)
409                 m_day = Day::TwoDigit;
410             break;
411         case 'h':
412         case 'H':
413         case 'k':
414         case 'K':
415             if (count == 1)
416                 m_hour = Hour::Numeric;
417             else if (count == 2)
418                 m_hour = Hour::TwoDigit;
419             break;
420         case 'm':
421             if (count == 1)
422                 m_minute = Minute::Numeric;
423             else if (count == 2)
424                 m_minute = Minute::TwoDigit;
425             break;
426         case 's':
427             if (count == 1)
428                 m_second = Second::Numeric;
429             else if (count == 2)
430                 m_second = Second::TwoDigit;
431             break;
432         case 'z':
433         case 'v':
434         case 'V':
435             if (count == 1)
436                 m_timeZoneName = TimeZoneName::Short;
437             else if (count == 4)
438                 m_timeZoneName = TimeZoneName::Long;
439             break;
440         }
441     }
442 }
443
444 void IntlDateTimeFormat::initializeDateTimeFormat(ExecState& exec, JSValue locales, JSValue originalOptions)
445 {
446     VM& vm = exec.vm();
447     auto scope = DECLARE_THROW_SCOPE(vm);
448
449     // 12.1.1 InitializeDateTimeFormat (dateTimeFormat, locales, options) (ECMA-402)
450     // https://tc39.github.io/ecma402/#sec-initializedatetimeformat
451
452     Vector<String> requestedLocales = canonicalizeLocaleList(exec, locales);
453     RETURN_IF_EXCEPTION(scope, void());
454
455     JSObject* options = IntlDTFInternal::toDateTimeOptionsAnyDate(exec, originalOptions);
456     RETURN_IF_EXCEPTION(scope, void());
457
458     HashMap<String, String> opt;
459
460     String localeMatcher = intlStringOption(exec, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit");
461     RETURN_IF_EXCEPTION(scope, void());
462     opt.add(vm.propertyNames->localeMatcher.string(), localeMatcher);
463
464     bool isHour12Undefined;
465     bool hour12 = intlBooleanOption(exec, options, vm.propertyNames->hour12, isHour12Undefined);
466     RETURN_IF_EXCEPTION(scope, void());
467
468     String hourCycle = intlStringOption(exec, options, vm.propertyNames->hourCycle, { "h11", "h12", "h23", "h24" }, "hourCycle must be \"h11\", \"h12\", \"h23\", or \"h24\"", nullptr);
469     RETURN_IF_EXCEPTION(scope, void());
470     if (isHour12Undefined) {
471         // Set hour12 here to simplify hour logic later.
472         hour12 = (hourCycle == "h11" || hourCycle == "h12");
473         if (!hourCycle.isNull())
474             opt.add("hc"_s, hourCycle);
475     } else
476         opt.add("hc"_s, String());
477
478     const HashSet<String> availableLocales = exec.jsCallee()->globalObject(vm)->intlDateTimeFormatAvailableLocales();
479     HashMap<String, String> resolved = resolveLocale(exec, availableLocales, requestedLocales, opt, IntlDTFInternal::relevantExtensionKeys, WTF_ARRAY_LENGTH(IntlDTFInternal::relevantExtensionKeys), IntlDTFInternal::localeData);
480
481     m_locale = resolved.get(vm.propertyNames->locale.string());
482     if (m_locale.isEmpty()) {
483         throwTypeError(&exec, scope, "failed to initialize DateTimeFormat due to invalid locale"_s);
484         return;
485     }
486
487     m_calendar = resolved.get("ca"_s);
488     if (m_calendar == "gregorian")
489         m_calendar = "gregory"_s;
490     else if (m_calendar == "islamicc")
491         m_calendar = "islamic-civil"_s;
492     else if (m_calendar == "ethioaa")
493         m_calendar = "ethiopic-amete-alem"_s;
494
495     m_hourCycle = resolved.get("hc"_s);
496     m_numberingSystem = resolved.get("nu"_s);
497     String dataLocale = resolved.get("dataLocale"_s);
498
499     JSValue tzValue = options->get(&exec, vm.propertyNames->timeZone);
500     RETURN_IF_EXCEPTION(scope, void());
501     String tz;
502     if (!tzValue.isUndefined()) {
503         String originalTz = tzValue.toWTFString(&exec);
504         RETURN_IF_EXCEPTION(scope, void());
505         tz = canonicalizeTimeZoneName(originalTz);
506         if (tz.isNull()) {
507             throwRangeError(&exec, scope, String::format("invalid time zone: %s", originalTz.utf8().data()));
508             return;
509         }
510     } else
511         tz = defaultTimeZone();
512     m_timeZone = tz;
513
514     StringBuilder skeletonBuilder;
515     auto narrowShortLong = { "narrow", "short", "long" };
516     auto twoDigitNumeric = { "2-digit", "numeric" };
517     auto twoDigitNumericNarrowShortLong = { "2-digit", "numeric", "narrow", "short", "long" };
518     auto shortLong = { "short", "long" };
519
520     String weekday = intlStringOption(exec, options, vm.propertyNames->weekday, narrowShortLong, "weekday must be \"narrow\", \"short\", or \"long\"", nullptr);
521     RETURN_IF_EXCEPTION(scope, void());
522     if (!weekday.isNull()) {
523         if (weekday == "narrow")
524             skeletonBuilder.appendLiteral("EEEEE");
525         else if (weekday == "short")
526             skeletonBuilder.appendLiteral("EEE");
527         else if (weekday == "long")
528             skeletonBuilder.appendLiteral("EEEE");
529     }
530
531     String era = intlStringOption(exec, options, vm.propertyNames->era, narrowShortLong, "era must be \"narrow\", \"short\", or \"long\"", nullptr);
532     RETURN_IF_EXCEPTION(scope, void());
533     if (!era.isNull()) {
534         if (era == "narrow")
535             skeletonBuilder.appendLiteral("GGGGG");
536         else if (era == "short")
537             skeletonBuilder.appendLiteral("GGG");
538         else if (era == "long")
539             skeletonBuilder.appendLiteral("GGGG");
540     }
541
542     String year = intlStringOption(exec, options, vm.propertyNames->year, twoDigitNumeric, "year must be \"2-digit\" or \"numeric\"", nullptr);
543     RETURN_IF_EXCEPTION(scope, void());
544     if (!year.isNull()) {
545         if (year == "2-digit")
546             skeletonBuilder.appendLiteral("yy");
547         else if (year == "numeric")
548             skeletonBuilder.append('y');
549     }
550
551     String month = intlStringOption(exec, options, vm.propertyNames->month, twoDigitNumericNarrowShortLong, "month must be \"2-digit\", \"numeric\", \"narrow\", \"short\", or \"long\"", nullptr);
552     RETURN_IF_EXCEPTION(scope, void());
553     if (!month.isNull()) {
554         if (month == "2-digit")
555             skeletonBuilder.appendLiteral("MM");
556         else if (month == "numeric")
557             skeletonBuilder.append('M');
558         else if (month == "narrow")
559             skeletonBuilder.appendLiteral("MMMMM");
560         else if (month == "short")
561             skeletonBuilder.appendLiteral("MMM");
562         else if (month == "long")
563             skeletonBuilder.appendLiteral("MMMM");
564     }
565
566     String day = intlStringOption(exec, options, vm.propertyNames->day, twoDigitNumeric, "day must be \"2-digit\" or \"numeric\"", nullptr);
567     RETURN_IF_EXCEPTION(scope, void());
568     if (!day.isNull()) {
569         if (day == "2-digit")
570             skeletonBuilder.appendLiteral("dd");
571         else if (day == "numeric")
572             skeletonBuilder.append('d');
573     }
574
575     String hour = intlStringOption(exec, options, vm.propertyNames->hour, twoDigitNumeric, "hour must be \"2-digit\" or \"numeric\"", nullptr);
576     RETURN_IF_EXCEPTION(scope, void());
577     if (hour == "2-digit") {
578         if (isHour12Undefined && m_hourCycle.isNull())
579             skeletonBuilder.appendLiteral("jj");
580         else if (hour12)
581             skeletonBuilder.appendLiteral("hh");
582         else
583             skeletonBuilder.appendLiteral("HH");
584     } else if (hour == "numeric") {
585         if (isHour12Undefined && m_hourCycle.isNull())
586             skeletonBuilder.append('j');
587         else if (hour12)
588             skeletonBuilder.append('h');
589         else
590             skeletonBuilder.append('H');
591     } else
592         m_hourCycle = String();
593
594     String minute = intlStringOption(exec, options, vm.propertyNames->minute, twoDigitNumeric, "minute must be \"2-digit\" or \"numeric\"", nullptr);
595     RETURN_IF_EXCEPTION(scope, void());
596     if (!minute.isNull()) {
597         if (minute == "2-digit")
598             skeletonBuilder.appendLiteral("mm");
599         else if (minute == "numeric")
600             skeletonBuilder.append('m');
601     }
602
603     String second = intlStringOption(exec, options, vm.propertyNames->second, twoDigitNumeric, "second must be \"2-digit\" or \"numeric\"", nullptr);
604     RETURN_IF_EXCEPTION(scope, void());
605     if (!second.isNull()) {
606         if (second == "2-digit")
607             skeletonBuilder.appendLiteral("ss");
608         else if (second == "numeric")
609             skeletonBuilder.append('s');
610     }
611
612     String timeZoneName = intlStringOption(exec, options, vm.propertyNames->timeZoneName, shortLong, "timeZoneName must be \"short\" or \"long\"", nullptr);
613     RETURN_IF_EXCEPTION(scope, void());
614     if (!timeZoneName.isNull()) {
615         if (timeZoneName == "short")
616             skeletonBuilder.append('z');
617         else if (timeZoneName == "long")
618             skeletonBuilder.appendLiteral("zzzz");
619     }
620
621     intlStringOption(exec, options, vm.propertyNames->formatMatcher, { "basic", "best fit" }, "formatMatcher must be either \"basic\" or \"best fit\"", "best fit");
622     RETURN_IF_EXCEPTION(scope, void());
623
624     // Always use ICU date format generator, rather than our own pattern list and matcher.
625     UErrorCode status = U_ZERO_ERROR;
626     UDateTimePatternGenerator* generator = udatpg_open(dataLocale.utf8().data(), &status);
627     if (U_FAILURE(status)) {
628         throwTypeError(&exec, scope, "failed to initialize DateTimeFormat"_s);
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, "failed to initialize DateTimeFormat"_s);
645         return;
646     }
647
648     // Enforce our hourCycle, replacing hour characters in pattern.
649     if (!m_hourCycle.isNull()) {
650         UChar hour = 'H';
651         if (m_hourCycle == "h11")
652             hour = 'K';
653         else if (m_hourCycle == "h12")
654             hour = 'h';
655         else if (m_hourCycle == "h24")
656             hour = 'k';
657
658         bool isEscaped = false;
659         bool hasHour = false;
660         for (auto i = 0; i < patternLength; ++i) {
661             UChar c = patternBuffer[i];
662             if (c == '\'')
663                 isEscaped = !isEscaped;
664             else if (!isEscaped && (c == 'h' || c == 'H' || c == 'k' || c == 'K')) {
665                 patternBuffer[i] = hour;
666                 hasHour = true;
667             }
668         }
669         if (!hasHour)
670             m_hourCycle = String();
671     }
672
673     StringView pattern(patternBuffer.data(), patternLength);
674     setFormatsFromPattern(pattern);
675
676     status = U_ZERO_ERROR;
677     StringView timeZoneView(m_timeZone);
678     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));
679     if (U_FAILURE(status)) {
680         throwTypeError(&exec, scope, "failed to initialize DateTimeFormat"_s);
681         return;
682     }
683
684     // Gregorian calendar should be used from the beginning of ECMAScript time.
685     // Failure here means unsupported calendar, and can safely be ignored.
686     UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(m_dateFormat.get()));
687     ucal_setGregorianChange(cal, minECMAScriptTime, &status);
688
689     m_initializedDateTimeFormat = true;
690 }
691
692 ASCIILiteral IntlDateTimeFormat::weekdayString(Weekday weekday)
693 {
694     switch (weekday) {
695     case Weekday::Narrow:
696         return "narrow"_s;
697     case Weekday::Short:
698         return "short"_s;
699     case Weekday::Long:
700         return "long"_s;
701     case Weekday::None:
702         ASSERT_NOT_REACHED();
703         return ASCIILiteral::null();
704     }
705     ASSERT_NOT_REACHED();
706     return ASCIILiteral::null();
707 }
708
709 ASCIILiteral IntlDateTimeFormat::eraString(Era era)
710 {
711     switch (era) {
712     case Era::Narrow:
713         return "narrow"_s;
714     case Era::Short:
715         return "short"_s;
716     case Era::Long:
717         return "long"_s;
718     case Era::None:
719         ASSERT_NOT_REACHED();
720         return ASCIILiteral::null();
721     }
722     ASSERT_NOT_REACHED();
723     return ASCIILiteral::null();
724 }
725
726 ASCIILiteral IntlDateTimeFormat::yearString(Year year)
727 {
728     switch (year) {
729     case Year::TwoDigit:
730         return "2-digit"_s;
731     case Year::Numeric:
732         return "numeric"_s;
733     case Year::None:
734         ASSERT_NOT_REACHED();
735         return ASCIILiteral::null();
736     }
737     ASSERT_NOT_REACHED();
738     return ASCIILiteral::null();
739 }
740
741 ASCIILiteral IntlDateTimeFormat::monthString(Month month)
742 {
743     switch (month) {
744     case Month::TwoDigit:
745         return "2-digit"_s;
746     case Month::Numeric:
747         return "numeric"_s;
748     case Month::Narrow:
749         return "narrow"_s;
750     case Month::Short:
751         return "short"_s;
752     case Month::Long:
753         return "long"_s;
754     case Month::None:
755         ASSERT_NOT_REACHED();
756         return ASCIILiteral::null();
757     }
758     ASSERT_NOT_REACHED();
759     return ASCIILiteral::null();
760 }
761
762 ASCIILiteral IntlDateTimeFormat::dayString(Day day)
763 {
764     switch (day) {
765     case Day::TwoDigit:
766         return "2-digit"_s;
767     case Day::Numeric:
768         return "numeric"_s;
769     case Day::None:
770         ASSERT_NOT_REACHED();
771         return ASCIILiteral::null();
772     }
773     ASSERT_NOT_REACHED();
774     return ASCIILiteral::null();
775 }
776
777 ASCIILiteral IntlDateTimeFormat::hourString(Hour hour)
778 {
779     switch (hour) {
780     case Hour::TwoDigit:
781         return "2-digit"_s;
782     case Hour::Numeric:
783         return "numeric"_s;
784     case Hour::None:
785         ASSERT_NOT_REACHED();
786         return ASCIILiteral::null();
787     }
788     ASSERT_NOT_REACHED();
789     return ASCIILiteral::null();
790 }
791
792 ASCIILiteral IntlDateTimeFormat::minuteString(Minute minute)
793 {
794     switch (minute) {
795     case Minute::TwoDigit:
796         return "2-digit"_s;
797     case Minute::Numeric:
798         return "numeric"_s;
799     case Minute::None:
800         ASSERT_NOT_REACHED();
801         return ASCIILiteral::null();
802     }
803     ASSERT_NOT_REACHED();
804     return ASCIILiteral::null();
805 }
806
807 ASCIILiteral IntlDateTimeFormat::secondString(Second second)
808 {
809     switch (second) {
810     case Second::TwoDigit:
811         return "2-digit"_s;
812     case Second::Numeric:
813         return "numeric"_s;
814     case Second::None:
815         ASSERT_NOT_REACHED();
816         return ASCIILiteral::null();
817     }
818     ASSERT_NOT_REACHED();
819     return ASCIILiteral::null();
820 }
821
822 ASCIILiteral IntlDateTimeFormat::timeZoneNameString(TimeZoneName timeZoneName)
823 {
824     switch (timeZoneName) {
825     case TimeZoneName::Short:
826         return "short"_s;
827     case TimeZoneName::Long:
828         return "long"_s;
829     case TimeZoneName::None:
830         ASSERT_NOT_REACHED();
831         return ASCIILiteral::null();
832     }
833     ASSERT_NOT_REACHED();
834     return ASCIILiteral::null();
835 }
836
837 JSObject* IntlDateTimeFormat::resolvedOptions(ExecState& exec)
838 {
839     VM& vm = exec.vm();
840     auto scope = DECLARE_THROW_SCOPE(vm);
841
842     // 12.3.5 Intl.DateTimeFormat.prototype.resolvedOptions() (ECMA-402 2.0)
843     // 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.
844     // 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.
845     if (!m_initializedDateTimeFormat) {
846         initializeDateTimeFormat(exec, jsUndefined(), jsUndefined());
847         scope.assertNoException();
848     }
849
850     JSObject* options = constructEmptyObject(&exec);
851     options->putDirect(vm, vm.propertyNames->locale, jsNontrivialString(&exec, m_locale));
852     options->putDirect(vm, vm.propertyNames->calendar, jsNontrivialString(&exec, m_calendar));
853     options->putDirect(vm, vm.propertyNames->numberingSystem, jsNontrivialString(&exec, m_numberingSystem));
854     options->putDirect(vm, vm.propertyNames->timeZone, jsNontrivialString(&exec, m_timeZone));
855
856     if (m_weekday != Weekday::None)
857         options->putDirect(vm, vm.propertyNames->weekday, jsNontrivialString(&exec, weekdayString(m_weekday)));
858
859     if (m_era != Era::None)
860         options->putDirect(vm, vm.propertyNames->era, jsNontrivialString(&exec, eraString(m_era)));
861
862     if (m_year != Year::None)
863         options->putDirect(vm, vm.propertyNames->year, jsNontrivialString(&exec, yearString(m_year)));
864
865     if (m_month != Month::None)
866         options->putDirect(vm, vm.propertyNames->month, jsNontrivialString(&exec, monthString(m_month)));
867
868     if (m_day != Day::None)
869         options->putDirect(vm, vm.propertyNames->day, jsNontrivialString(&exec, dayString(m_day)));
870
871     if (m_hour != Hour::None)
872         options->putDirect(vm, vm.propertyNames->hour, jsNontrivialString(&exec, hourString(m_hour)));
873
874     if (!m_hourCycle.isNull()) {
875         options->putDirect(vm, vm.propertyNames->hourCycle, jsNontrivialString(&exec, m_hourCycle));
876         options->putDirect(vm, vm.propertyNames->hour12, jsBoolean(m_hourCycle == "h11" || m_hourCycle == "h12"));
877     }
878
879     if (m_minute != Minute::None)
880         options->putDirect(vm, vm.propertyNames->minute, jsNontrivialString(&exec, minuteString(m_minute)));
881
882     if (m_second != Second::None)
883         options->putDirect(vm, vm.propertyNames->second, jsNontrivialString(&exec, secondString(m_second)));
884
885     if (m_timeZoneName != TimeZoneName::None)
886         options->putDirect(vm, vm.propertyNames->timeZoneName, jsNontrivialString(&exec, timeZoneNameString(m_timeZoneName)));
887
888     return options;
889 }
890
891 JSValue IntlDateTimeFormat::format(ExecState& exec, double value)
892 {
893     VM& vm = exec.vm();
894     auto scope = DECLARE_THROW_SCOPE(vm);
895
896     // 12.3.4 FormatDateTime abstract operation (ECMA-402 2.0)
897     if (!m_initializedDateTimeFormat) {
898         initializeDateTimeFormat(exec, jsUndefined(), jsUndefined());
899         scope.assertNoException();
900     }
901
902     // 1. If x is not a finite Number, then throw a RangeError exception.
903     if (!std::isfinite(value))
904         return throwRangeError(&exec, scope, "date value is not finite in DateTimeFormat format()"_s);
905
906     // Delegate remaining steps to ICU.
907     UErrorCode status = U_ZERO_ERROR;
908     Vector<UChar, 32> result(32);
909     auto resultLength = udat_format(m_dateFormat.get(), value, result.data(), result.size(), nullptr, &status);
910     if (status == U_BUFFER_OVERFLOW_ERROR) {
911         status = U_ZERO_ERROR;
912         result.grow(resultLength);
913         udat_format(m_dateFormat.get(), value, result.data(), resultLength, nullptr, &status);
914     }
915     if (U_FAILURE(status))
916         return throwTypeError(&exec, scope, "failed to format date value"_s);
917
918     return jsString(&exec, String(result.data(), resultLength));
919 }
920
921 #if JSC_ICU_HAS_UFIELDPOSITER
922 ASCIILiteral IntlDateTimeFormat::partTypeString(UDateFormatField field)
923 {
924     switch (field) {
925     case UDAT_ERA_FIELD:
926         return "era"_s;
927     case UDAT_YEAR_FIELD:
928     case UDAT_YEAR_NAME_FIELD:
929     case UDAT_EXTENDED_YEAR_FIELD:
930         return "year"_s;
931     case UDAT_MONTH_FIELD:
932     case UDAT_STANDALONE_MONTH_FIELD:
933         return "month"_s;
934     case UDAT_DATE_FIELD:
935         return "day"_s;
936     case UDAT_HOUR_OF_DAY1_FIELD:
937     case UDAT_HOUR_OF_DAY0_FIELD:
938     case UDAT_HOUR1_FIELD:
939     case UDAT_HOUR0_FIELD:
940         return "hour"_s;
941     case UDAT_MINUTE_FIELD:
942         return "minute"_s;
943     case UDAT_SECOND_FIELD:
944     case UDAT_FRACTIONAL_SECOND_FIELD:
945         return "second"_s;
946     case UDAT_DAY_OF_WEEK_FIELD:
947     case UDAT_DOW_LOCAL_FIELD:
948     case UDAT_STANDALONE_DAY_FIELD:
949         return "weekday"_s;
950     case UDAT_AM_PM_FIELD:
951 #if U_ICU_VERSION_MAJOR_NUM >= 57
952     case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
953     case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
954 #endif
955         return "dayPeriod"_s;
956     case UDAT_TIMEZONE_FIELD:
957     case UDAT_TIMEZONE_RFC_FIELD:
958     case UDAT_TIMEZONE_GENERIC_FIELD:
959     case UDAT_TIMEZONE_SPECIAL_FIELD:
960     case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
961     case UDAT_TIMEZONE_ISO_FIELD:
962     case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
963         return "timeZoneName"_s;
964     // These should not show up because there is no way to specify them in DateTimeFormat options.
965     // If they do, they don't fit well into any of known part types, so consider it an "unknown".
966     case UDAT_DAY_OF_YEAR_FIELD:
967     case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD:
968     case UDAT_WEEK_OF_YEAR_FIELD:
969     case UDAT_WEEK_OF_MONTH_FIELD:
970     case UDAT_YEAR_WOY_FIELD:
971     case UDAT_JULIAN_DAY_FIELD:
972     case UDAT_MILLISECONDS_IN_DAY_FIELD:
973     case UDAT_QUARTER_FIELD:
974     case UDAT_STANDALONE_QUARTER_FIELD:
975     case UDAT_RELATED_YEAR_FIELD:
976     case UDAT_TIME_SEPARATOR_FIELD:
977 #if U_ICU_VERSION_MAJOR_NUM < 58 || !defined(U_HIDE_DEPRECATED_API)
978     case UDAT_FIELD_COUNT:
979 #endif
980         return "unknown"_s;
981     }
982     // Any newer additions to the UDateFormatField enum should just be considered an "unknown" part.
983     return "unknown"_s;
984 }
985
986
987 JSValue IntlDateTimeFormat::formatToParts(ExecState& exec, double value)
988 {
989     VM& vm = exec.vm();
990     auto scope = DECLARE_THROW_SCOPE(vm);
991
992     // 12.1.8 FormatDateTimeToParts (ECMA-402 4.0)
993     // https://tc39.github.io/ecma402/#sec-formatdatetimetoparts
994
995     if (!std::isfinite(value))
996         return throwRangeError(&exec, scope, "date value is not finite in DateTimeFormat formatToParts()"_s);
997
998     UErrorCode status = U_ZERO_ERROR;
999     auto fields = std::unique_ptr<UFieldPositionIterator, UFieldPositionIteratorDeleter>(ufieldpositer_open(&status));
1000     if (U_FAILURE(status))
1001         return throwTypeError(&exec, scope, "failed to open field position iterator"_s);
1002
1003     status = U_ZERO_ERROR;
1004     Vector<UChar, 32> result(32);
1005     auto resultLength = udat_formatForFields(m_dateFormat.get(), value, result.data(), result.size(), fields.get(), &status);
1006     if (status == U_BUFFER_OVERFLOW_ERROR) {
1007         status = U_ZERO_ERROR;
1008         result.grow(resultLength);
1009         udat_formatForFields(m_dateFormat.get(), value, result.data(), resultLength, fields.get(), &status);
1010     }
1011     if (U_FAILURE(status))
1012         return throwTypeError(&exec, scope, "failed to format date value"_s);
1013
1014     JSGlobalObject* globalObject = exec.jsCallee()->globalObject(vm);
1015     JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
1016     if (!parts)
1017         return throwOutOfMemoryError(&exec, scope);
1018
1019     auto resultString = String(result.data(), resultLength);
1020     auto typePropertyName = Identifier::fromString(&vm, "type");
1021     auto literalString = jsString(&exec, "literal"_s);
1022
1023     int32_t previousEndIndex = 0;
1024     int32_t beginIndex = 0;
1025     int32_t endIndex = 0;
1026     while (previousEndIndex < resultLength) {
1027         auto fieldType = ufieldpositer_next(fields.get(), &beginIndex, &endIndex);
1028         if (fieldType < 0)
1029             beginIndex = endIndex = resultLength;
1030
1031         if (previousEndIndex < beginIndex) {
1032             auto value = jsString(&exec, resultString.substring(previousEndIndex, beginIndex - previousEndIndex));
1033             JSObject* part = constructEmptyObject(&exec);
1034             part->putDirect(vm, typePropertyName, literalString);
1035             part->putDirect(vm, vm.propertyNames->value, value);
1036             parts->push(&exec, part);
1037             RETURN_IF_EXCEPTION(scope, { });
1038         }
1039         previousEndIndex = endIndex;
1040
1041         if (fieldType >= 0) {
1042             auto type = jsString(&exec, partTypeString(UDateFormatField(fieldType)));
1043             auto value = jsString(&exec, resultString.substring(beginIndex, endIndex - beginIndex));
1044             JSObject* part = constructEmptyObject(&exec);
1045             part->putDirect(vm, typePropertyName, type);
1046             part->putDirect(vm, vm.propertyNames->value, value);
1047             parts->push(&exec, part);
1048             RETURN_IF_EXCEPTION(scope, { });
1049         }
1050     }
1051
1052
1053     return parts;
1054 }
1055 #endif
1056
1057 } // namespace JSC
1058
1059 #endif // ENABLE(INTL)