JSValue IntlDateTimeFormat::format(ExecState& exec, double value) { VM& vm = exec.vm(); auto scope = DECLARE_THROW_SCOPE(vm); // 12.3.4 FormatDateTime abstract operation (ECMA-402 2.0) if (!m_initializedDateTimeFormat) { initializeDateTimeFormat(exec, jsUndefined(), jsUndefined()); ASSERT(!scope.exception()); } // 1. If x is not a finite Number, then throw a RangeError exception. if (!std::isfinite(value)) return throwRangeError(&exec, scope, ASCIILiteral("date value is not finite in DateTimeFormat format()")); // Delegate remaining steps to ICU. UErrorCode status = U_ZERO_ERROR; Vector<UChar, 32> result(32); auto resultLength = udat_format(m_dateFormat.get(), value, result.data(), result.size(), nullptr, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; result.grow(resultLength); udat_format(m_dateFormat.get(), value, result.data(), resultLength, nullptr, &status); } if (U_FAILURE(status)) return throwTypeError(&exec, scope, ASCIILiteral("failed to format date value")); return jsString(&exec, String(result.data(), resultLength)); }
EncodedJSValue JSC_HOST_CALL IntlDateTimeFormatFuncFormatDateTime(ExecState* state) { // 12.3.4 DateTime Format Functions (ECMA-402 2.0) // 1. Let dtf be the this value. IntlDateTimeFormat* format = jsDynamicCast<IntlDateTimeFormat*>(state->thisValue()); // 2. Assert: Type(dtf) is Object and dtf has an [[initializedDateTimeFormat]] internal slot whose value is true. if (!format) return JSValue::encode(throwTypeError(state)); JSValue date = state->argument(0); double value; // 3. If date is not provided or is undefined, then if (date.isUndefined()) { // a. Let x be %Date_now%(). value = JSValue::decode(dateNow(state)).toNumber(state); } else { // 4. Else // a. Let x be ToNumber(date). value = date.toNumber(state); // b. ReturnIfAbrupt(x). if (state->hadException()) return JSValue::encode(jsUndefined()); } // 5. Return FormatDateTime(dtf, x). // 12.3.4 FormatDateTime abstract operation (ECMA-402 2.0) // 1. If x is not a finite Number, then throw a RangeError exception. if (!std::isfinite(value)) return JSValue::encode(throwRangeError(state, ASCIILiteral("date value is not finite in DateTimeFormat.format()"))); // FIXME: implement 2 - 9 // Return new Date(value).toString() until properly implemented. VM& vm = state->vm(); JSGlobalObject* globalObject = state->callee()->globalObject(); DateInstance* d = DateInstance::create(vm, globalObject->dateStructure(), value); return JSValue::encode(JSValue(d).toString(state)); }
void IntlDateTimeFormat::initializeDateTimeFormat(ExecState& exec, JSValue locales, JSValue originalOptions) { VM& vm = exec.vm(); auto scope = DECLARE_THROW_SCOPE(vm); // 12.1.1 InitializeDateTimeFormat (dateTimeFormat, locales, options) (ECMA-402 2.0) // 1. If dateTimeFormat.[[initializedIntlObject]] is true, throw a TypeError exception. // 2. Set dateTimeFormat.[[initializedIntlObject]] to true. // 3. Let requestedLocales be CanonicalizeLocaleList(locales). Vector<String> requestedLocales = canonicalizeLocaleList(exec, locales); // 4. ReturnIfAbrupt(requestedLocales), RETURN_IF_EXCEPTION(scope, void()); // 5. Let options be ToDateTimeOptions(options, "any", "date"). JSObject* options = toDateTimeOptionsAnyDate(exec, originalOptions); // 6. ReturnIfAbrupt(options). RETURN_IF_EXCEPTION(scope, void()); // 7. Let opt be a new Record. HashMap<String, String> localeOpt; // 8. Let matcher be GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit"). String localeMatcher = intlStringOption(exec, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit"); // 9. ReturnIfAbrupt(matcher). RETURN_IF_EXCEPTION(scope, void()); // 10. Set opt.[[localeMatcher]] to matcher. localeOpt.add(vm.propertyNames->localeMatcher.string(), localeMatcher); // 11. Let localeData be the value of %DateTimeFormat%.[[localeData]]. // 12. Let r be ResolveLocale( %DateTimeFormat%.[[availableLocales]], requestedLocales, opt, %DateTimeFormat%.[[relevantExtensionKeys]], localeData). const HashSet<String> availableLocales = exec.jsCallee()->globalObject()->intlDateTimeFormatAvailableLocales(); HashMap<String, String> resolved = resolveLocale(exec, availableLocales, requestedLocales, localeOpt, relevantExtensionKeys, WTF_ARRAY_LENGTH(relevantExtensionKeys), localeData); // 13. Set dateTimeFormat.[[locale]] to the value of r.[[locale]]. m_locale = resolved.get(vm.propertyNames->locale.string()); if (m_locale.isEmpty()) { throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat due to invalid locale")); return; } // 14. Set dateTimeFormat.[[calendar]] to the value of r.[[ca]]. m_calendar = resolved.get(ASCIILiteral("ca")); // Switch to preferred aliases. if (m_calendar == "gregory") m_calendar = ASCIILiteral("gregorian"); else if (m_calendar == "islamicc") m_calendar = ASCIILiteral("islamic-civil"); else if (m_calendar == "ethioaa") m_calendar = ASCIILiteral("ethiopic-amete-alem"); // 15. Set dateTimeFormat.[[numberingSystem]] to the value of r.[[nu]]. m_numberingSystem = resolved.get(ASCIILiteral("nu")); // 16. Let dataLocale be the value of r.[[dataLocale]]. String dataLocale = resolved.get(ASCIILiteral("dataLocale")); // 17. Let tz be Get(options, "timeZone"). JSValue tzValue = options->get(&exec, vm.propertyNames->timeZone); // 18. ReturnIfAbrupt(tz). RETURN_IF_EXCEPTION(scope, void()); // 19. If tz is not undefined, then String tz; if (!tzValue.isUndefined()) { // a. Let tz be ToString(tz). String originalTz = tzValue.toWTFString(&exec); // b. ReturnIfAbrupt(tz). RETURN_IF_EXCEPTION(scope, void()); // c. If the result of IsValidTimeZoneName(tz) is false, then i. Throw a RangeError exception. // d. Let tz be CanonicalizeTimeZoneName(tz). tz = canonicalizeTimeZoneName(originalTz); if (tz.isNull()) { throwRangeError(&exec, scope, String::format("invalid time zone: %s", originalTz.utf8().data())); return; } } else { // 20. Else, // a. Let tz be DefaultTimeZone(). tz = defaultTimeZone(); } // 21. Set dateTimeFormat.[[timeZone]] to tz. m_timeZone = tz; // 22. Let opt be a new Record. // Rather than building a record, build the skeleton pattern. StringBuilder skeletonBuilder; // 23. For each row of Table 3, except the header row, do: // a. Let prop be the name given in the Property column of the row. // b. Let value be GetOption(options, prop, "string", «the strings given in the Values column of the row», undefined). // c. ReturnIfAbrupt(value). // d. Set opt.[[<prop>]] to value. auto narrowShortLong = { "narrow", "short", "long" }; auto twoDigitNumeric = { "2-digit", "numeric" }; auto twoDigitNumericNarrowShortLong = { "2-digit", "numeric", "narrow", "short", "long" }; auto shortLong = { "short", "long" }; String weekday = intlStringOption(exec, options, vm.propertyNames->weekday, narrowShortLong, "weekday must be \"narrow\", \"short\", or \"long\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!weekday.isNull()) { if (weekday == "narrow") skeletonBuilder.appendLiteral("EEEEE"); else if (weekday == "short") skeletonBuilder.appendLiteral("EEE"); else if (weekday == "long") skeletonBuilder.appendLiteral("EEEE"); } String era = intlStringOption(exec, options, vm.propertyNames->era, narrowShortLong, "era must be \"narrow\", \"short\", or \"long\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!era.isNull()) { if (era == "narrow") skeletonBuilder.appendLiteral("GGGGG"); else if (era == "short") skeletonBuilder.appendLiteral("GGG"); else if (era == "long") skeletonBuilder.appendLiteral("GGGG"); } String year = intlStringOption(exec, options, vm.propertyNames->year, twoDigitNumeric, "year must be \"2-digit\" or \"numeric\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!year.isNull()) { if (year == "2-digit") skeletonBuilder.appendLiteral("yy"); else if (year == "numeric") skeletonBuilder.append('y'); } String month = intlStringOption(exec, options, vm.propertyNames->month, twoDigitNumericNarrowShortLong, "month must be \"2-digit\", \"numeric\", \"narrow\", \"short\", or \"long\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!month.isNull()) { if (month == "2-digit") skeletonBuilder.appendLiteral("MM"); else if (month == "numeric") skeletonBuilder.append('M'); else if (month == "narrow") skeletonBuilder.appendLiteral("MMMMM"); else if (month == "short") skeletonBuilder.appendLiteral("MMM"); else if (month == "long") skeletonBuilder.appendLiteral("MMMM"); } String day = intlStringOption(exec, options, vm.propertyNames->day, twoDigitNumeric, "day must be \"2-digit\" or \"numeric\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!day.isNull()) { if (day == "2-digit") skeletonBuilder.appendLiteral("dd"); else if (day == "numeric") skeletonBuilder.append('d'); } String hour = intlStringOption(exec, options, vm.propertyNames->hour, twoDigitNumeric, "hour must be \"2-digit\" or \"numeric\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); // We need hour12 to make the hour skeleton pattern decision, so do this early. // 32. Let hr12 be GetOption(options, "hour12", "boolean", undefined, undefined). bool isHour12Undefined; bool hr12 = intlBooleanOption(exec, options, vm.propertyNames->hour12, isHour12Undefined); // 33. ReturnIfAbrupt(hr12). RETURN_IF_EXCEPTION(scope, void()); if (!hour.isNull()) { if (isHour12Undefined) { if (hour == "2-digit") skeletonBuilder.appendLiteral("jj"); else if (hour == "numeric") skeletonBuilder.append('j'); } else if (hr12) { if (hour == "2-digit") skeletonBuilder.appendLiteral("hh"); else if (hour == "numeric") skeletonBuilder.append('h'); } else { if (hour == "2-digit") skeletonBuilder.appendLiteral("HH"); else if (hour == "numeric") skeletonBuilder.append('H'); } } String minute = intlStringOption(exec, options, vm.propertyNames->minute, twoDigitNumeric, "minute must be \"2-digit\" or \"numeric\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!minute.isNull()) { if (minute == "2-digit") skeletonBuilder.appendLiteral("mm"); else if (minute == "numeric") skeletonBuilder.append('m'); } String second = intlStringOption(exec, options, vm.propertyNames->second, twoDigitNumeric, "second must be \"2-digit\" or \"numeric\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!second.isNull()) { if (second == "2-digit") skeletonBuilder.appendLiteral("ss"); else if (second == "numeric") skeletonBuilder.append('s'); } String timeZoneName = intlStringOption(exec, options, vm.propertyNames->timeZoneName, shortLong, "timeZoneName must be \"short\" or \"long\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!timeZoneName.isNull()) { if (timeZoneName == "short") skeletonBuilder.append('z'); else if (timeZoneName == "long") skeletonBuilder.appendLiteral("zzzz"); } // 24. Let dataLocaleData be Get(localeData, dataLocale). // 25. Let formats be Get(dataLocaleData, "formats"). // 26. Let matcher be GetOption(options, "formatMatcher", "string", «"basic", "best fit"», "best fit"). intlStringOption(exec, options, vm.propertyNames->formatMatcher, { "basic", "best fit" }, "formatMatcher must be either \"basic\" or \"best fit\"", "best fit"); // 27. ReturnIfAbrupt(matcher). RETURN_IF_EXCEPTION(scope, void()); // Always use ICU date format generator, rather than our own pattern list and matcher. // Covers steps 28-36. UErrorCode status = U_ZERO_ERROR; UDateTimePatternGenerator* generator = udatpg_open(dataLocale.utf8().data(), &status); if (U_FAILURE(status)) { throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat")); return; } String skeleton = skeletonBuilder.toString(); StringView skeletonView(skeleton); Vector<UChar, 32> patternBuffer(32); status = U_ZERO_ERROR; auto patternLength = udatpg_getBestPattern(generator, skeletonView.upconvertedCharacters(), skeletonView.length(), patternBuffer.data(), patternBuffer.size(), &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; patternBuffer.grow(patternLength); udatpg_getBestPattern(generator, skeletonView.upconvertedCharacters(), skeletonView.length(), patternBuffer.data(), patternLength, &status); } udatpg_close(generator); if (U_FAILURE(status)) { throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat")); return; } StringView pattern(patternBuffer.data(), patternLength); setFormatsFromPattern(pattern); status = U_ZERO_ERROR; StringView timeZoneView(m_timeZone); 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)); if (U_FAILURE(status)) { throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat")); return; } // 37. Set dateTimeFormat.[[boundFormat]] to undefined. // Already undefined. // 38. Set dateTimeFormat.[[initializedDateTimeFormat]] to true. m_initializedDateTimeFormat = true; // 39. Return dateTimeFormat. }