diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index dac996e603bd..6b9df9bffec4 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -30,10 +30,10 @@ #include "builtin/intl/Collator.h" #include "builtin/intl/CommonFunctions.h" +#include "builtin/intl/DateTimeFormat.h" #include "builtin/intl/ICUStubs.h" #include "builtin/intl/NumberFormat.h" #include "builtin/intl/ScopedICUObject.h" -#include "builtin/intl/SharedIntlData.h" #include "ds/Sort.h" #include "gc/FreeOp.h" #include "js/Date.h" @@ -54,902 +54,16 @@ using namespace js; using mozilla::AssertedCast; using mozilla::IsNegativeZero; -using mozilla::PodCopy; using mozilla::Range; using mozilla::RangedPtr; -using JS::ClippedTime; -using JS::TimeClip; - using js::intl::CallICU; using js::intl::DateTimeFormatOptions; using js::intl::GetAvailableLocales; using js::intl::IcuLocale; using js::intl::INITIAL_CHAR_BUFFER_SIZE; -using js::intl::SharedIntlData; using js::intl::StringsAreEqual; -/******************** DateTimeFormat ********************/ - -const ClassOps DateTimeFormatObject::classOps_ = { - nullptr, /* addProperty */ - nullptr, /* delProperty */ - nullptr, /* enumerate */ - nullptr, /* newEnumerate */ - nullptr, /* resolve */ - nullptr, /* mayResolve */ - DateTimeFormatObject::finalize -}; - -const Class DateTimeFormatObject::class_ = { - js_Object_str, - JSCLASS_HAS_RESERVED_SLOTS(DateTimeFormatObject::SLOT_COUNT) | - JSCLASS_FOREGROUND_FINALIZE, - &DateTimeFormatObject::classOps_ -}; - -#if JS_HAS_TOSOURCE -static bool -dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - args.rval().setString(cx->names().DateTimeFormat); - return true; -} -#endif - -static const JSFunctionSpec dateTimeFormat_static_methods[] = { - JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_DateTimeFormat_supportedLocalesOf", 1, 0), - JS_FS_END -}; - -static const JSFunctionSpec dateTimeFormat_methods[] = { - JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions", 0, 0), - JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 1, 0), -#if JS_HAS_TOSOURCE - JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0), -#endif - JS_FS_END -}; - -static const JSPropertySpec dateTimeFormat_properties[] = { - JS_SELF_HOSTED_GET("format", "Intl_DateTimeFormat_format_get", 0), - JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), - JS_PS_END -}; - -/** - * 12.2.1 Intl.DateTimeFormat([ locales [, options]]) - * - * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b - */ -static bool -DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct, DateTimeFormatOptions dtfOptions) -{ - // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code). - - // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). - RootedObject proto(cx); - if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) - return false; - - if (!proto) { - proto = GlobalObject::getOrCreateDateTimeFormatPrototype(cx, cx->global()); - if (!proto) - return false; - } - - Rooted dateTimeFormat(cx); - dateTimeFormat = NewObjectWithGivenProto(cx, proto); - if (!dateTimeFormat) - return false; - - dateTimeFormat->setReservedSlot(DateTimeFormatObject::INTERNALS_SLOT, NullValue()); - dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, - PrivateValue(nullptr)); - - RootedValue thisValue(cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv()); - HandleValue locales = args.get(0); - HandleValue options = args.get(1); - - // Step 3. - return intl::LegacyInitializeObject(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat, - thisValue, locales, options, dtfOptions, args.rval()); -} - -static bool -DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - return DateTimeFormat(cx, args, args.isConstructing(), DateTimeFormatOptions::Standard); -} - -static bool -MozDateTimeFormat(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - // Don't allow to call mozIntl.DateTimeFormat as a function. That way we - // don't need to worry how to handle the legacy initialization semantics - // when applied on mozIntl.DateTimeFormat. - if (!ThrowIfNotConstructing(cx, args, "mozIntl.DateTimeFormat")) - return false; - - return DateTimeFormat(cx, args, true, DateTimeFormatOptions::EnableMozExtensions); -} - -bool -js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 2); - MOZ_ASSERT(!args.isConstructing()); - // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it - // cannot be used with "new", but it still has to be treated as a - // constructor. - return DateTimeFormat(cx, args, true, DateTimeFormatOptions::Standard); -} - -void -DateTimeFormatObject::finalize(FreeOp* fop, JSObject* obj) -{ - MOZ_ASSERT(fop->onActiveCooperatingThread()); - - const Value& slot = - obj->as().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT); - if (UDateFormat* df = static_cast(slot.toPrivate())) - udat_close(df); -} - -static JSObject* -CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle global, - MutableHandleObject constructor, DateTimeFormatOptions dtfOptions) -{ - RootedFunction ctor(cx); - ctor = dtfOptions == DateTimeFormatOptions::EnableMozExtensions - ? GlobalObject::createConstructor(cx, MozDateTimeFormat, cx->names().DateTimeFormat, 0) - : GlobalObject::createConstructor(cx, DateTimeFormat, cx->names().DateTimeFormat, 0); - if (!ctor) - return nullptr; - - RootedObject proto(cx, GlobalObject::createBlankPrototype(cx, global)); - if (!proto) - return nullptr; - - if (!LinkConstructorAndPrototype(cx, ctor, proto)) - return nullptr; - - // 12.3.2 - if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods)) - return nullptr; - - // 12.4.4 and 12.4.5 - if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods)) - return nullptr; - - // 12.4.2 and 12.4.3 - if (!JS_DefineProperties(cx, proto, dateTimeFormat_properties)) - return nullptr; - - // 8.1 - RootedValue ctorValue(cx, ObjectValue(*ctor)); - if (!DefineDataProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, 0)) - return nullptr; - - constructor.set(ctor); - return proto; -} - -bool -js::AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle intl) -{ - Handle global = cx->global(); - - RootedObject mozDateTimeFormat(cx); - JSObject* mozDateTimeFormatProto = - CreateDateTimeFormatPrototype(cx, intl, global, &mozDateTimeFormat, DateTimeFormatOptions::EnableMozExtensions); - return mozDateTimeFormatProto != nullptr; -} - -bool -js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 0); - - RootedValue result(cx); - if (!GetAvailableLocales(cx, udat_countAvailable, udat_getAvailable, &result)) - return false; - args.rval().set(result); - return true; -} - -static bool -DefaultCalendar(JSContext* cx, const JSAutoByteString& locale, MutableHandleValue rval) -{ - UErrorCode status = U_ZERO_ERROR; - UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status); - - // This correctly handles nullptr |cal| when opening failed. - ScopedICUObject closeCalendar(cal); - - const char* calendar = ucal_getType(cal, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - - // ICU returns old-style keyword values; map them to BCP 47 equivalents - JSString* str = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("ca", calendar)); - if (!str) - return false; - - rval.setString(str); - return true; -} - -struct CalendarAlias -{ - const char* const calendar; - const char* const alias; -}; - -const CalendarAlias calendarAliases[] = { - { "islamic-civil", "islamicc" }, - { "ethioaa", "ethiopic-amete-alem" } -}; - -bool -js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 1); - MOZ_ASSERT(args[0].isString()); - - JSAutoByteString locale(cx, args[0].toString()); - if (!locale) - return false; - - RootedObject calendars(cx, NewDenseEmptyArray(cx)); - if (!calendars) - return false; - uint32_t index = 0; - - // We need the default calendar for the locale as the first result. - RootedValue element(cx); - if (!DefaultCalendar(cx, locale, &element)) - return false; - - if (!DefineDataElement(cx, calendars, index++, element)) - return false; - - // Now get the calendars that "would make a difference", i.e., not the default. - UErrorCode status = U_ZERO_ERROR; - UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - ScopedICUObject toClose(values); - - uint32_t count = uenum_count(values, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - - for (; count > 0; count--) { - const char* calendar = uenum_next(values, nullptr, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - - // ICU returns old-style keyword values; map them to BCP 47 equivalents - calendar = uloc_toUnicodeLocaleType("ca", calendar); - - JSString* jscalendar = JS_NewStringCopyZ(cx, calendar); - if (!jscalendar) - return false; - element = StringValue(jscalendar); - if (!DefineDataElement(cx, calendars, index++, element)) - return false; - - // ICU doesn't return calendar aliases, append them here. - for (const auto& calendarAlias : calendarAliases) { - if (StringsAreEqual(calendar, calendarAlias.calendar)) { - JSString* jscalendar = JS_NewStringCopyZ(cx, calendarAlias.alias); - if (!jscalendar) - return false; - element = StringValue(jscalendar); - if (!DefineDataElement(cx, calendars, index++, element)) - return false; - } - } - } - - args.rval().setObject(*calendars); - return true; -} - -bool -js::intl_defaultCalendar(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 1); - MOZ_ASSERT(args[0].isString()); - - JSAutoByteString locale(cx, args[0].toString()); - if (!locale) - return false; - - return DefaultCalendar(cx, locale, args.rval()); -} - -bool -js::intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 1); - MOZ_ASSERT(args[0].isString()); - - SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); - - RootedString timeZone(cx, args[0].toString()); - RootedAtom validatedTimeZone(cx); - if (!sharedIntlData.validateTimeZoneName(cx, timeZone, &validatedTimeZone)) - return false; - - if (validatedTimeZone) { - cx->markAtom(validatedTimeZone); - args.rval().setString(validatedTimeZone); - } else { - args.rval().setNull(); - } - - return true; -} - -bool -js::intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 1); - MOZ_ASSERT(args[0].isString()); - - SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); - - // Some time zone names are canonicalized differently by ICU -- handle - // those first: - RootedString timeZone(cx, args[0].toString()); - RootedAtom ianaTimeZone(cx); - if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA(cx, timeZone, &ianaTimeZone)) - return false; - - if (ianaTimeZone) { - cx->markAtom(ianaTimeZone); - args.rval().setString(ianaTimeZone); - return true; - } - - AutoStableStringChars stableChars(cx); - if (!stableChars.initTwoByte(cx, timeZone)) - return false; - - mozilla::Range tzchars = stableChars.twoByteRange(); - - JSString* str = CallICU(cx, [&tzchars](UChar* chars, uint32_t size, UErrorCode* status) { - return ucal_getCanonicalTimeZoneID(tzchars.begin().get(), tzchars.length(), - chars, size, nullptr, status); - }); - if (!str) - return false; - - args.rval().setString(str); - return true; -} - -bool -js::intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 0); - - // The current default might be stale, because JS::ResetTimeZone() doesn't - // immediately update ICU's default time zone. So perform an update if - // needed. - js::ResyncICUDefaultTimeZone(); - - JSString* str = CallICU(cx, ucal_getDefaultTimeZone); - if (!str) - return false; - - args.rval().setString(str); - return true; -} - -bool -js::intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 0); - - UErrorCode status = U_ZERO_ERROR; - const UChar* uTimeZone = nullptr; - int32_t uTimeZoneLength = 0; - const char* rootLocale = ""; - UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, rootLocale, UCAL_DEFAULT, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - ScopedICUObject toClose(cal); - - int32_t offset = ucal_get(cal, UCAL_ZONE_OFFSET, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - - args.rval().setInt32(offset); - return true; -} - -bool -js::intl_isDefaultTimeZone(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 1); - MOZ_ASSERT(args[0].isString() || args[0].isUndefined()); - - // |undefined| is the default value when the Intl runtime caches haven't - // yet been initialized. Handle it the same way as a cache miss. - if (args[0].isUndefined()) { - args.rval().setBoolean(false); - return true; - } - - // The current default might be stale, because JS::ResetTimeZone() doesn't - // immediately update ICU's default time zone. So perform an update if - // needed. - js::ResyncICUDefaultTimeZone(); - - Vector chars(cx); - int32_t size = CallICU(cx, ucal_getDefaultTimeZone, chars); - if (size < 0) - return false; - - JSLinearString* str = args[0].toString()->ensureLinear(cx); - if (!str) - return false; - - bool equals; - if (str->length() == size_t(size)) { - JS::AutoCheckCannotGC nogc; - equals = str->hasLatin1Chars() - ? EqualChars(str->latin1Chars(nogc), chars.begin(), str->length()) - : EqualChars(str->twoByteChars(nogc), chars.begin(), str->length()); - } else { - equals = false; - } - - args.rval().setBoolean(equals); - return true; -} - -bool -js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 2); - MOZ_ASSERT(args[0].isString()); - MOZ_ASSERT(args[1].isString()); - - JSAutoByteString locale(cx, args[0].toString()); - if (!locale) - return false; - - AutoStableStringChars skeleton(cx); - if (!skeleton.initTwoByte(cx, args[1].toString())) - return false; - - mozilla::Range skelChars = skeleton.twoByteRange(); - - UErrorCode status = U_ZERO_ERROR; - UDateTimePatternGenerator* gen = udatpg_open(IcuLocale(locale.ptr()), &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - ScopedICUObject toClose(gen); - - JSString* str = - CallICU(cx, [gen, &skelChars](UChar* chars, uint32_t size, UErrorCode* status) { - return udatpg_getBestPattern(gen, skelChars.begin().get(), skelChars.length(), - chars, size, status); - }); - if (!str) - return false; - - args.rval().setString(str); - return true; -} - -bool -js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 4); - MOZ_ASSERT(args[0].isString()); - - JSAutoByteString locale(cx, args[0].toString()); - if (!locale) - return false; - - UDateFormatStyle dateStyle = UDAT_NONE; - UDateFormatStyle timeStyle = UDAT_NONE; - - if (args[1].isString()) { - JSLinearString* dateStyleStr = args[1].toString()->ensureLinear(cx); - if (!dateStyleStr) - return false; - - if (StringEqualsAscii(dateStyleStr, "full")) - dateStyle = UDAT_FULL; - else if (StringEqualsAscii(dateStyleStr, "long")) - dateStyle = UDAT_LONG; - else if (StringEqualsAscii(dateStyleStr, "medium")) - dateStyle = UDAT_MEDIUM; - else if (StringEqualsAscii(dateStyleStr, "short")) - dateStyle = UDAT_SHORT; - else - MOZ_ASSERT_UNREACHABLE("unexpected dateStyle"); - } - - if (args[2].isString()) { - JSLinearString* timeStyleStr = args[2].toString()->ensureLinear(cx); - if (!timeStyleStr) - return false; - - if (StringEqualsAscii(timeStyleStr, "full")) - timeStyle = UDAT_FULL; - else if (StringEqualsAscii(timeStyleStr, "long")) - timeStyle = UDAT_LONG; - else if (StringEqualsAscii(timeStyleStr, "medium")) - timeStyle = UDAT_MEDIUM; - else if (StringEqualsAscii(timeStyleStr, "short")) - timeStyle = UDAT_SHORT; - else - MOZ_ASSERT_UNREACHABLE("unexpected timeStyle"); - } - - AutoStableStringChars timeZone(cx); - if (!timeZone.initTwoByte(cx, args[3].toString())) - return false; - - mozilla::Range timeZoneChars = timeZone.twoByteRange(); - - UErrorCode status = U_ZERO_ERROR; - UDateFormat* df = udat_open(timeStyle, dateStyle, IcuLocale(locale.ptr()), - timeZoneChars.begin().get(), timeZoneChars.length(), - nullptr, -1, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - ScopedICUObject toClose(df); - - JSString* str = CallICU(cx, [df](UChar* chars, uint32_t size, UErrorCode* status) { - return udat_toPattern(df, false, chars, size, status); - }); - if (!str) - return false; - args.rval().setString(str); - return true; -} - -/** - * Returns a new UDateFormat with the locale and date-time formatting options - * of the given DateTimeFormat. - */ -static UDateFormat* -NewUDateFormat(JSContext* cx, Handle dateTimeFormat) -{ - RootedValue value(cx); - - RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat)); - if (!internals) - return nullptr; - - if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) - return nullptr; - JSAutoByteString locale(cx, value.toString()); - if (!locale) - return nullptr; - - // We don't need to look at calendar and numberingSystem - they can only be - // set via the Unicode locale extension and are therefore already set on - // locale. - - if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value)) - return nullptr; - - AutoStableStringChars timeZone(cx); - if (!timeZone.initTwoByte(cx, value.toString())) - return nullptr; - - mozilla::Range timeZoneChars = timeZone.twoByteRange(); - - if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) - return nullptr; - - AutoStableStringChars pattern(cx); - if (!pattern.initTwoByte(cx, value.toString())) - return nullptr; - - mozilla::Range patternChars = pattern.twoByteRange(); - - UErrorCode status = U_ZERO_ERROR; - UDateFormat* df = - udat_open(UDAT_PATTERN, UDAT_PATTERN, IcuLocale(locale.ptr()), - timeZoneChars.begin().get(), timeZoneChars.length(), - patternChars.begin().get(), patternChars.length(), &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return nullptr; - } - - // ECMAScript requires the Gregorian calendar to be used from the beginning - // of ECMAScript time. - UCalendar* cal = const_cast(udat_getCalendar(df)); - ucal_setGregorianChange(cal, StartOfTime, &status); - - // An error here means the calendar is not Gregorian, so we don't care. - - return df; -} - -static bool -intl_FormatDateTime(JSContext* cx, UDateFormat* df, ClippedTime x, MutableHandleValue result) -{ - MOZ_ASSERT(x.isValid()); - - JSString* str = CallICU(cx, [df, x](UChar* chars, int32_t size, UErrorCode* status) { - return udat_format(df, x.toDouble(), chars, size, nullptr, status); - }); - if (!str) - return false; - - result.setString(str); - return true; -} - -using FieldType = ImmutablePropertyNamePtr JSAtomState::*; - -static FieldType -GetFieldTypeForFormatField(UDateFormatField fieldName) -{ - // See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This - // switch is deliberately exhaustive: cases might have to be added/removed - // if this code is compiled with a different ICU with more - // UDateFormatField enum initializers. Please guard such cases with - // appropriate ICU version-testing #ifdefs, should cross-version divergence - // occur. - switch (fieldName) { - case UDAT_ERA_FIELD: - return &JSAtomState::era; - case UDAT_YEAR_FIELD: - case UDAT_YEAR_WOY_FIELD: - case UDAT_EXTENDED_YEAR_FIELD: - case UDAT_YEAR_NAME_FIELD: - return &JSAtomState::year; - - case UDAT_MONTH_FIELD: - case UDAT_STANDALONE_MONTH_FIELD: - return &JSAtomState::month; - - case UDAT_DATE_FIELD: - case UDAT_JULIAN_DAY_FIELD: - return &JSAtomState::day; - - case UDAT_HOUR_OF_DAY1_FIELD: - case UDAT_HOUR_OF_DAY0_FIELD: - case UDAT_HOUR1_FIELD: - case UDAT_HOUR0_FIELD: - return &JSAtomState::hour; - - case UDAT_MINUTE_FIELD: - return &JSAtomState::minute; - - case UDAT_SECOND_FIELD: - return &JSAtomState::second; - - case UDAT_DAY_OF_WEEK_FIELD: - case UDAT_STANDALONE_DAY_FIELD: - case UDAT_DOW_LOCAL_FIELD: - case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD: - return &JSAtomState::weekday; - - case UDAT_AM_PM_FIELD: - return &JSAtomState::dayPeriod; - - case UDAT_TIMEZONE_FIELD: - return &JSAtomState::timeZoneName; - - case UDAT_FRACTIONAL_SECOND_FIELD: - case UDAT_DAY_OF_YEAR_FIELD: - case UDAT_WEEK_OF_YEAR_FIELD: - case UDAT_WEEK_OF_MONTH_FIELD: - case UDAT_MILLISECONDS_IN_DAY_FIELD: - case UDAT_TIMEZONE_RFC_FIELD: - case UDAT_TIMEZONE_GENERIC_FIELD: - case UDAT_QUARTER_FIELD: - case UDAT_STANDALONE_QUARTER_FIELD: - case UDAT_TIMEZONE_SPECIAL_FIELD: - case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: - case UDAT_TIMEZONE_ISO_FIELD: - case UDAT_TIMEZONE_ISO_LOCAL_FIELD: -#ifndef U_HIDE_INTERNAL_API - case UDAT_RELATED_YEAR_FIELD: -#endif - case UDAT_AM_PM_MIDNIGHT_NOON_FIELD: - case UDAT_FLEXIBLE_DAY_PERIOD_FIELD: -#ifndef U_HIDE_INTERNAL_API - case UDAT_TIME_SEPARATOR_FIELD: -#endif - // These fields are all unsupported. - return nullptr; - -#ifndef U_HIDE_DEPRECATED_API - case UDAT_FIELD_COUNT: - MOZ_ASSERT_UNREACHABLE("format field sentinel value returned by " - "iterator!"); -#endif - } - - MOZ_ASSERT_UNREACHABLE("unenumerated, undocumented format field returned " - "by iterator"); - return nullptr; -} - -static bool -intl_FormatToPartsDateTime(JSContext* cx, UDateFormat* df, ClippedTime x, - MutableHandleValue result) -{ - MOZ_ASSERT(x.isValid()); - - UErrorCode status = U_ZERO_ERROR; - UFieldPositionIterator* fpositer = ufieldpositer_open(&status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - ScopedICUObject toClose(fpositer); - - RootedString overallResult(cx); - overallResult = CallICU(cx, [df, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) { - return udat_formatForFields(df, x.toDouble(), chars, size, fpositer, status); - }); - if (!overallResult) - return false; - - RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx)); - if (!partsArray) - return false; - - if (overallResult->length() == 0) { - // An empty string contains no parts, so avoid extra work below. - result.setObject(*partsArray); - return true; - } - - size_t lastEndIndex = 0; - - uint32_t partIndex = 0; - RootedObject singlePart(cx); - RootedValue partType(cx); - RootedValue val(cx); - - auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) { - singlePart = NewBuiltinClassInstance(cx); - if (!singlePart) - return false; - - partType = StringValue(cx->names().*type); - if (!DefineDataProperty(cx, singlePart, cx->names().type, partType)) - return false; - - JSLinearString* partSubstr = - NewDependentString(cx, overallResult, beginIndex, endIndex - beginIndex); - if (!partSubstr) - return false; - - val = StringValue(partSubstr); - if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) - return false; - - val = ObjectValue(*singlePart); - if (!DefineDataElement(cx, partsArray, partIndex, val)) - return false; - - lastEndIndex = endIndex; - partIndex++; - return true; - }; - - int32_t fieldInt, beginIndexInt, endIndexInt; - while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt, &endIndexInt)) >= 0) { - MOZ_ASSERT(beginIndexInt >= 0); - MOZ_ASSERT(endIndexInt >= 0); - MOZ_ASSERT(beginIndexInt <= endIndexInt, - "field iterator returning invalid range"); - - size_t beginIndex(beginIndexInt); - size_t endIndex(endIndexInt); - - // Technically this isn't guaranteed. But it appears true in pratice, - // and http://bugs.icu-project.org/trac/ticket/12024 is expected to - // correct the documentation lapse. - MOZ_ASSERT(lastEndIndex <= beginIndex, - "field iteration didn't return fields in order start to " - "finish as expected"); - - if (FieldType type = GetFieldTypeForFormatField(static_cast(fieldInt))) { - if (lastEndIndex < beginIndex) { - if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) - return false; - } - - if (!AppendPart(type, beginIndex, endIndex)) - return false; - } - } - - // Append any final literal. - if (lastEndIndex < overallResult->length()) { - if (!AppendPart(&JSAtomState::literal, lastEndIndex, overallResult->length())) - return false; - } - - result.setObject(*partsArray); - return true; -} - -bool -js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 3); - MOZ_ASSERT(args[0].isObject()); - MOZ_ASSERT(args[1].isNumber()); - MOZ_ASSERT(args[2].isBoolean()); - - Rooted dateTimeFormat(cx); - dateTimeFormat = &args[0].toObject().as(); - - ClippedTime x = TimeClip(args[1].toNumber()); - if (!x.isValid()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE); - return false; - } - - // Obtain a cached UDateFormat object. - void* priv = - dateTimeFormat->getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT).toPrivate(); - UDateFormat* df = static_cast(priv); - if (!df) { - df = NewUDateFormat(cx, dateTimeFormat); - if (!df) - return false; - dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(df)); - } - - // Use the UDateFormat to actually format the time stamp. - return args[2].toBoolean() - ? intl_FormatToPartsDateTime(cx, df, x, args.rval()) - : intl_FormatDateTime(cx, df, x, args.rval()); -} - - /**************** PluralRules *****************/ const ClassOps PluralRulesObject::classOps_ = { @@ -2214,7 +1328,8 @@ GlobalObject::initIntlObject(JSContext* cx, Handle global) if (!collatorProto) return false; RootedObject dateTimeFormatProto(cx), dateTimeFormat(cx); - dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat, DateTimeFormatOptions::Standard); + dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat, + DateTimeFormatOptions::Standard); if (!dateTimeFormatProto) return false; RootedObject numberFormatProto(cx), numberFormat(cx); diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h index 3c5dc40a082e..68e61c9bfef4 100644 --- a/js/src/builtin/Intl.h +++ b/js/src/builtin/Intl.h @@ -41,168 +41,6 @@ InitIntlClass(JSContext* cx, HandleObject obj); */ -/******************** DateTimeFormat ********************/ - -class DateTimeFormatObject : public NativeObject -{ - public: - static const Class class_; - - static constexpr uint32_t INTERNALS_SLOT = 0; - static constexpr uint32_t UDATE_FORMAT_SLOT = 1; - static constexpr uint32_t SLOT_COUNT = 2; - - static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT, - "INTERNALS_SLOT must match self-hosting define for internals object slot"); - - private: - static const ClassOps classOps_; - - static void finalize(FreeOp* fop, JSObject* obj); -}; - -/** - * Returns a new instance of the standard built-in DateTimeFormat constructor. - * Self-hosted code cannot cache this constructor (as it does for others in - * Utilities.js) because it is initialized after self-hosted code is compiled. - * - * Usage: dateTimeFormat = intl_DateTimeFormat(locales, options) - */ -extern MOZ_MUST_USE bool -intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp); - -/** - * Returns an object indicating the supported locales for date and time - * formatting by having a true-valued property for each such locale with the - * canonicalized language tag as the property name. The object has no - * prototype. - * - * Usage: availableLocales = intl_DateTimeFormat_availableLocales() - */ -extern MOZ_MUST_USE bool -intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp); - -/** - * Returns an array with the calendar type identifiers per Unicode - * Technical Standard 35, Unicode Locale Data Markup Language, for the - * supported calendars for the given locale. The default calendar is - * element 0. - * - * Usage: calendars = intl_availableCalendars(locale) - */ -extern MOZ_MUST_USE bool -intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp); - -/** - * Returns the calendar type identifier per Unicode Technical Standard 35, - * Unicode Locale Data Markup Language, for the default calendar for the given - * locale. - * - * Usage: calendar = intl_defaultCalendar(locale) - */ -extern MOZ_MUST_USE bool -intl_defaultCalendar(JSContext* cx, unsigned argc, Value* vp); - -/** - * 6.4.1 IsValidTimeZoneName ( timeZone ) - * - * Verifies that the given string is a valid time zone name. If it is a valid - * time zone name, its IANA time zone name is returned. Otherwise returns null. - * - * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3 - * - * Usage: ianaTimeZone = intl_IsValidTimeZoneName(timeZone) - */ -extern MOZ_MUST_USE bool -intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp); - -/** - * Return the canonicalized time zone name. Canonicalization resolves link - * names to their target time zones. - * - * Usage: ianaTimeZone = intl_canonicalizeTimeZone(timeZone) - */ -extern MOZ_MUST_USE bool -intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp); - -/** - * Return the default time zone name. The time zone name is not canonicalized. - * - * Usage: icuDefaultTimeZone = intl_defaultTimeZone() - */ -extern MOZ_MUST_USE bool -intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp); - -/** - * Return the raw offset from GMT in milliseconds for the default time zone. - * - * Usage: defaultTimeZoneOffset = intl_defaultTimeZoneOffset() - */ -extern MOZ_MUST_USE bool -intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp); - -/** - * Return true if the given string is the default time zone as returned by - * intl_defaultTimeZone(). Otherwise return false. - * - * Usage: isIcuDefaultTimeZone = intl_isDefaultTimeZone(icuDefaultTimeZone) - */ -extern MOZ_MUST_USE bool -intl_isDefaultTimeZone(JSContext* cx, unsigned argc, Value* vp); - -/** - * Return a pattern in the date-time format pattern language of Unicode - * Technical Standard 35, Unicode Locale Data Markup Language, for the - * best-fit date-time format pattern corresponding to skeleton for the - * given locale. - * - * Usage: pattern = intl_patternForSkeleton(locale, skeleton) - */ -extern MOZ_MUST_USE bool -intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp); - -/** - * Return a pattern in the date-time format pattern language of Unicode - * Technical Standard 35, Unicode Locale Data Markup Language, for the - * best-fit date-time style for the given locale. - * The function takes four arguments: - * - * locale - * BCP47 compliant locale string - * dateStyle - * A string with values: full or long or medium or short, or `undefined` - * timeStyle - * A string with values: full or long or medium or short, or `undefined` - * timeZone - * IANA time zone name - * - * Date and time style categories map to CLDR time/date standard - * format patterns. - * - * For the definition of a pattern string, see LDML 4.8: - * http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns - * - * If `undefined` is passed to `dateStyle` or `timeStyle`, the respective - * portions of the pattern will not be included in the result. - * - * Usage: pattern = intl_patternForStyle(locale, dateStyle, timeStyle, timeZone) - */ -extern MOZ_MUST_USE bool -intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp); - -/** - * Returns a String value representing x (which must be a Number value) - * according to the effective locale and the formatting options of the - * given DateTimeFormat. - * - * Spec: ECMAScript Internationalization API Specification, 12.3.2. - * - * Usage: formatted = intl_FormatDateTime(dateTimeFormat, x, formatToParts) - */ -extern MOZ_MUST_USE bool -intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp); - - /******************** PluralRules ********************/ class PluralRulesObject : public NativeObject diff --git a/js/src/builtin/intl/DateTimeFormat.cpp b/js/src/builtin/intl/DateTimeFormat.cpp new file mode 100644 index 000000000000..e799d45036b3 --- /dev/null +++ b/js/src/builtin/intl/DateTimeFormat.cpp @@ -0,0 +1,922 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Intl.DateTimeFormat implementation. */ + +#include "builtin/intl/DateTimeFormat.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Range.h" + +#include "jscntxt.h" +#include "jsfriendapi.h" + +#include "builtin/intl/CommonFunctions.h" +#include "builtin/intl/ICUStubs.h" +#include "builtin/intl/ScopedICUObject.h" +#include "builtin/intl/SharedIntlData.h" +#include "builtin/IntlTimeZoneData.h" +#include "gc/FreeOp.h" +#include "vm/GlobalObject.h" +#include "vm/Runtime.h" + +#include "jsobjinlines.h" + +#include "vm/NativeObject-inl.h" + +using namespace js; + +using JS::ClippedTime; +using JS::TimeClip; + +using js::intl::CallICU; +using js::intl::DateTimeFormatOptions; +using js::intl::GetAvailableLocales; +using js::intl::IcuLocale; +using js::intl::INITIAL_CHAR_BUFFER_SIZE; +using js::intl::SharedIntlData; +using js::intl::StringsAreEqual; + +const ClassOps DateTimeFormatObject::classOps_ = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* enumerate */ + nullptr, /* newEnumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + DateTimeFormatObject::finalize +}; + +const Class DateTimeFormatObject::class_ = { + js_Object_str, + JSCLASS_HAS_RESERVED_SLOTS(DateTimeFormatObject::SLOT_COUNT) | + JSCLASS_FOREGROUND_FINALIZE, + &DateTimeFormatObject::classOps_ +}; + +#if JS_HAS_TOSOURCE +static bool +dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(cx->names().DateTimeFormat); + return true; +} +#endif + +static const JSFunctionSpec dateTimeFormat_static_methods[] = { + JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_DateTimeFormat_supportedLocalesOf", 1, 0), + JS_FS_END +}; + +static const JSFunctionSpec dateTimeFormat_methods[] = { + JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions", 0, 0), + JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 1, 0), +#if JS_HAS_TOSOURCE + JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0), +#endif + JS_FS_END +}; + +static const JSPropertySpec dateTimeFormat_properties[] = { + JS_SELF_HOSTED_GET("format", "Intl_DateTimeFormat_format_get", 0), + JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), + JS_PS_END +}; + +/** + * 12.2.1 Intl.DateTimeFormat([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b + */ +static bool +DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct, DateTimeFormatOptions dtfOptions) +{ + // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code). + + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) + return false; + + if (!proto) { + proto = GlobalObject::getOrCreateDateTimeFormatPrototype(cx, cx->global()); + if (!proto) + return false; + } + + Rooted dateTimeFormat(cx); + dateTimeFormat = NewObjectWithGivenProto(cx, proto); + if (!dateTimeFormat) + return false; + + dateTimeFormat->setReservedSlot(DateTimeFormatObject::INTERNALS_SLOT, NullValue()); + dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, + PrivateValue(nullptr)); + + RootedValue thisValue(cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv()); + HandleValue locales = args.get(0); + HandleValue options = args.get(1); + + // Step 3. + return intl::LegacyInitializeObject(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat, + thisValue, locales, options, dtfOptions, args.rval()); +} + +static bool +DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return DateTimeFormat(cx, args, args.isConstructing(), DateTimeFormatOptions::Standard); +} + +static bool +MozDateTimeFormat(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Don't allow to call mozIntl.DateTimeFormat as a function. That way we + // don't need to worry how to handle the legacy initialization semantics + // when applied on mozIntl.DateTimeFormat. + if (!ThrowIfNotConstructing(cx, args, "mozIntl.DateTimeFormat")) + return false; + + return DateTimeFormat(cx, args, true, DateTimeFormatOptions::EnableMozExtensions); +} + +bool +js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(!args.isConstructing()); + // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it + // cannot be used with "new", but it still has to be treated as a + // constructor. + return DateTimeFormat(cx, args, true, DateTimeFormatOptions::Standard); +} + +void +js::DateTimeFormatObject::finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(fop->onActiveCooperatingThread()); + + const Value& slot = + obj->as().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT); + if (UDateFormat* df = static_cast(slot.toPrivate())) + udat_close(df); +} + +JSObject* +js::CreateDateTimeFormatPrototype(JSContext* cx, JS::Handle Intl, + JS::Handle global, + JS::MutableHandle constructor, + DateTimeFormatOptions dtfOptions) +{ + RootedFunction ctor(cx); + ctor = dtfOptions == DateTimeFormatOptions::EnableMozExtensions + ? GlobalObject::createConstructor(cx, MozDateTimeFormat, cx->names().DateTimeFormat, 0) + : GlobalObject::createConstructor(cx, DateTimeFormat, cx->names().DateTimeFormat, 0); + if (!ctor) + return nullptr; + + RootedObject proto(cx, GlobalObject::createBlankPrototype(cx, global)); + if (!proto) + return nullptr; + + if (!LinkConstructorAndPrototype(cx, ctor, proto)) + return nullptr; + + // 12.3.2 + if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods)) + return nullptr; + + // 12.4.4 and 12.4.5 + if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods)) + return nullptr; + + // 12.4.2 and 12.4.3 + if (!JS_DefineProperties(cx, proto, dateTimeFormat_properties)) + return nullptr; + + // 8.1 + RootedValue ctorValue(cx, ObjectValue(*ctor)); + if (!DefineDataProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, 0)) + return nullptr; + + constructor.set(ctor); + return proto; +} + +bool +js::AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle intl) +{ + Handle global = cx->global(); + + RootedObject mozDateTimeFormat(cx); + JSObject* mozDateTimeFormatProto = + CreateDateTimeFormatPrototype(cx, intl, global, &mozDateTimeFormat, + DateTimeFormatOptions::EnableMozExtensions); + return mozDateTimeFormatProto != nullptr; +} + +bool +js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + RootedValue result(cx); + if (!GetAvailableLocales(cx, udat_countAvailable, udat_getAvailable, &result)) + return false; + args.rval().set(result); + return true; +} + +static bool +DefaultCalendar(JSContext* cx, const JSAutoByteString& locale, MutableHandleValue rval) +{ + UErrorCode status = U_ZERO_ERROR; + UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status); + + // This correctly handles nullptr |cal| when opening failed. + ScopedICUObject closeCalendar(cal); + + const char* calendar = ucal_getType(cal, &status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + + // ICU returns old-style keyword values; map them to BCP 47 equivalents + JSString* str = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("ca", calendar)); + if (!str) + return false; + + rval.setString(str); + return true; +} + +struct CalendarAlias +{ + const char* const calendar; + const char* const alias; +}; + +const CalendarAlias calendarAliases[] = { + { "islamic-civil", "islamicc" }, + { "ethioaa", "ethiopic-amete-alem" } +}; + +bool +js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString()); + + JSAutoByteString locale(cx, args[0].toString()); + if (!locale) + return false; + + RootedObject calendars(cx, NewDenseEmptyArray(cx)); + if (!calendars) + return false; + uint32_t index = 0; + + // We need the default calendar for the locale as the first result. + RootedValue element(cx); + if (!DefaultCalendar(cx, locale, &element)) + return false; + + if (!DefineDataElement(cx, calendars, index++, element)) + return false; + + // Now get the calendars that "would make a difference", i.e., not the default. + UErrorCode status = U_ZERO_ERROR; + UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + ScopedICUObject toClose(values); + + uint32_t count = uenum_count(values, &status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + + for (; count > 0; count--) { + const char* calendar = uenum_next(values, nullptr, &status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + + // ICU returns old-style keyword values; map them to BCP 47 equivalents + calendar = uloc_toUnicodeLocaleType("ca", calendar); + + JSString* jscalendar = JS_NewStringCopyZ(cx, calendar); + if (!jscalendar) + return false; + element = StringValue(jscalendar); + if (!DefineDataElement(cx, calendars, index++, element)) + return false; + + // ICU doesn't return calendar aliases, append them here. + for (const auto& calendarAlias : calendarAliases) { + if (StringsAreEqual(calendar, calendarAlias.calendar)) { + JSString* jscalendar = JS_NewStringCopyZ(cx, calendarAlias.alias); + if (!jscalendar) + return false; + element = StringValue(jscalendar); + if (!DefineDataElement(cx, calendars, index++, element)) + return false; + } + } + } + + args.rval().setObject(*calendars); + return true; +} + +bool +js::intl_defaultCalendar(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString()); + + JSAutoByteString locale(cx, args[0].toString()); + if (!locale) + return false; + + return DefaultCalendar(cx, locale, args.rval()); +} + +bool +js::intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString()); + + SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); + + RootedString timeZone(cx, args[0].toString()); + RootedAtom validatedTimeZone(cx); + if (!sharedIntlData.validateTimeZoneName(cx, timeZone, &validatedTimeZone)) + return false; + + if (validatedTimeZone) { + cx->markAtom(validatedTimeZone); + args.rval().setString(validatedTimeZone); + } else { + args.rval().setNull(); + } + + return true; +} + +bool +js::intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString()); + + SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); + + // Some time zone names are canonicalized differently by ICU -- handle + // those first: + RootedString timeZone(cx, args[0].toString()); + RootedAtom ianaTimeZone(cx); + if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA(cx, timeZone, &ianaTimeZone)) + return false; + + if (ianaTimeZone) { + cx->markAtom(ianaTimeZone); + args.rval().setString(ianaTimeZone); + return true; + } + + AutoStableStringChars stableChars(cx); + if (!stableChars.initTwoByte(cx, timeZone)) + return false; + + mozilla::Range tzchars = stableChars.twoByteRange(); + + JSString* str = CallICU(cx, [&tzchars](UChar* chars, uint32_t size, UErrorCode* status) { + return ucal_getCanonicalTimeZoneID(tzchars.begin().get(), tzchars.length(), + chars, size, nullptr, status); + }); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + +bool +js::intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + // The current default might be stale, because JS::ResetTimeZone() doesn't + // immediately update ICU's default time zone. So perform an update if + // needed. + js::ResyncICUDefaultTimeZone(); + + JSString* str = CallICU(cx, ucal_getDefaultTimeZone); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + +bool +js::intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + UErrorCode status = U_ZERO_ERROR; + const UChar* uTimeZone = nullptr; + int32_t uTimeZoneLength = 0; + const char* rootLocale = ""; + UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, rootLocale, UCAL_DEFAULT, &status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + ScopedICUObject toClose(cal); + + int32_t offset = ucal_get(cal, UCAL_ZONE_OFFSET, &status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + + args.rval().setInt32(offset); + return true; +} + +bool +js::intl_isDefaultTimeZone(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString() || args[0].isUndefined()); + + // |undefined| is the default value when the Intl runtime caches haven't + // yet been initialized. Handle it the same way as a cache miss. + if (args[0].isUndefined()) { + args.rval().setBoolean(false); + return true; + } + + // The current default might be stale, because JS::ResetTimeZone() doesn't + // immediately update ICU's default time zone. So perform an update if + // needed. + js::ResyncICUDefaultTimeZone(); + + Vector chars(cx); + int32_t size = CallICU(cx, ucal_getDefaultTimeZone, chars); + if (size < 0) + return false; + + JSLinearString* str = args[0].toString()->ensureLinear(cx); + if (!str) + return false; + + bool equals; + if (str->length() == size_t(size)) { + JS::AutoCheckCannotGC nogc; + equals = str->hasLatin1Chars() + ? EqualChars(str->latin1Chars(nogc), chars.begin(), str->length()) + : EqualChars(str->twoByteChars(nogc), chars.begin(), str->length()); + } else { + equals = false; + } + + args.rval().setBoolean(equals); + return true; +} + +bool +js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(args[0].isString()); + MOZ_ASSERT(args[1].isString()); + + JSAutoByteString locale(cx, args[0].toString()); + if (!locale) + return false; + + AutoStableStringChars skeleton(cx); + if (!skeleton.initTwoByte(cx, args[1].toString())) + return false; + + mozilla::Range skelChars = skeleton.twoByteRange(); + + UErrorCode status = U_ZERO_ERROR; + UDateTimePatternGenerator* gen = udatpg_open(IcuLocale(locale.ptr()), &status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + ScopedICUObject toClose(gen); + + JSString* str = + CallICU(cx, [gen, &skelChars](UChar* chars, uint32_t size, UErrorCode* status) { + return udatpg_getBestPattern(gen, skelChars.begin().get(), skelChars.length(), + chars, size, status); + }); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + +bool +js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 4); + MOZ_ASSERT(args[0].isString()); + + JSAutoByteString locale(cx, args[0].toString()); + if (!locale) + return false; + + UDateFormatStyle dateStyle = UDAT_NONE; + UDateFormatStyle timeStyle = UDAT_NONE; + + if (args[1].isString()) { + JSLinearString* dateStyleStr = args[1].toString()->ensureLinear(cx); + if (!dateStyleStr) + return false; + + if (StringEqualsAscii(dateStyleStr, "full")) + dateStyle = UDAT_FULL; + else if (StringEqualsAscii(dateStyleStr, "long")) + dateStyle = UDAT_LONG; + else if (StringEqualsAscii(dateStyleStr, "medium")) + dateStyle = UDAT_MEDIUM; + else if (StringEqualsAscii(dateStyleStr, "short")) + dateStyle = UDAT_SHORT; + else + MOZ_ASSERT_UNREACHABLE("unexpected dateStyle"); + } + + if (args[2].isString()) { + JSLinearString* timeStyleStr = args[2].toString()->ensureLinear(cx); + if (!timeStyleStr) + return false; + + if (StringEqualsAscii(timeStyleStr, "full")) + timeStyle = UDAT_FULL; + else if (StringEqualsAscii(timeStyleStr, "long")) + timeStyle = UDAT_LONG; + else if (StringEqualsAscii(timeStyleStr, "medium")) + timeStyle = UDAT_MEDIUM; + else if (StringEqualsAscii(timeStyleStr, "short")) + timeStyle = UDAT_SHORT; + else + MOZ_ASSERT_UNREACHABLE("unexpected timeStyle"); + } + + AutoStableStringChars timeZone(cx); + if (!timeZone.initTwoByte(cx, args[3].toString())) + return false; + + mozilla::Range timeZoneChars = timeZone.twoByteRange(); + + UErrorCode status = U_ZERO_ERROR; + UDateFormat* df = udat_open(timeStyle, dateStyle, IcuLocale(locale.ptr()), + timeZoneChars.begin().get(), timeZoneChars.length(), + nullptr, -1, &status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + ScopedICUObject toClose(df); + + JSString* str = CallICU(cx, [df](UChar* chars, uint32_t size, UErrorCode* status) { + return udat_toPattern(df, false, chars, size, status); + }); + if (!str) + return false; + args.rval().setString(str); + return true; +} + +/** + * Returns a new UDateFormat with the locale and date-time formatting options + * of the given DateTimeFormat. + */ +static UDateFormat* +NewUDateFormat(JSContext* cx, Handle dateTimeFormat) +{ + RootedValue value(cx); + + RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat)); + if (!internals) + return nullptr; + + if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) + return nullptr; + JSAutoByteString locale(cx, value.toString()); + if (!locale) + return nullptr; + + // We don't need to look at calendar and numberingSystem - they can only be + // set via the Unicode locale extension and are therefore already set on + // locale. + + if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value)) + return nullptr; + + AutoStableStringChars timeZone(cx); + if (!timeZone.initTwoByte(cx, value.toString())) + return nullptr; + + mozilla::Range timeZoneChars = timeZone.twoByteRange(); + + if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) + return nullptr; + + AutoStableStringChars pattern(cx); + if (!pattern.initTwoByte(cx, value.toString())) + return nullptr; + + mozilla::Range patternChars = pattern.twoByteRange(); + + UErrorCode status = U_ZERO_ERROR; + UDateFormat* df = + udat_open(UDAT_PATTERN, UDAT_PATTERN, IcuLocale(locale.ptr()), + timeZoneChars.begin().get(), timeZoneChars.length(), + patternChars.begin().get(), patternChars.length(), &status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return nullptr; + } + + // ECMAScript requires the Gregorian calendar to be used from the beginning + // of ECMAScript time. + UCalendar* cal = const_cast(udat_getCalendar(df)); + ucal_setGregorianChange(cal, StartOfTime, &status); + + // An error here means the calendar is not Gregorian, so we don't care. + + return df; +} + +static bool +intl_FormatDateTime(JSContext* cx, UDateFormat* df, ClippedTime x, MutableHandleValue result) +{ + MOZ_ASSERT(x.isValid()); + + JSString* str = CallICU(cx, [df, x](UChar* chars, int32_t size, UErrorCode* status) { + return udat_format(df, x.toDouble(), chars, size, nullptr, status); + }); + if (!str) + return false; + + result.setString(str); + return true; +} + +using FieldType = js::ImmutablePropertyNamePtr JSAtomState::*; + +static FieldType +GetFieldTypeForFormatField(UDateFormatField fieldName) +{ + // See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This + // switch is deliberately exhaustive: cases might have to be added/removed + // if this code is compiled with a different ICU with more + // UDateFormatField enum initializers. Please guard such cases with + // appropriate ICU version-testing #ifdefs, should cross-version divergence + // occur. + switch (fieldName) { + case UDAT_ERA_FIELD: + return &JSAtomState::era; + case UDAT_YEAR_FIELD: + case UDAT_YEAR_WOY_FIELD: + case UDAT_EXTENDED_YEAR_FIELD: + case UDAT_YEAR_NAME_FIELD: + return &JSAtomState::year; + + case UDAT_MONTH_FIELD: + case UDAT_STANDALONE_MONTH_FIELD: + return &JSAtomState::month; + + case UDAT_DATE_FIELD: + case UDAT_JULIAN_DAY_FIELD: + return &JSAtomState::day; + + case UDAT_HOUR_OF_DAY1_FIELD: + case UDAT_HOUR_OF_DAY0_FIELD: + case UDAT_HOUR1_FIELD: + case UDAT_HOUR0_FIELD: + return &JSAtomState::hour; + + case UDAT_MINUTE_FIELD: + return &JSAtomState::minute; + + case UDAT_SECOND_FIELD: + return &JSAtomState::second; + + case UDAT_DAY_OF_WEEK_FIELD: + case UDAT_STANDALONE_DAY_FIELD: + case UDAT_DOW_LOCAL_FIELD: + case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD: + return &JSAtomState::weekday; + + case UDAT_AM_PM_FIELD: + return &JSAtomState::dayPeriod; + + case UDAT_TIMEZONE_FIELD: + return &JSAtomState::timeZoneName; + + case UDAT_FRACTIONAL_SECOND_FIELD: + case UDAT_DAY_OF_YEAR_FIELD: + case UDAT_WEEK_OF_YEAR_FIELD: + case UDAT_WEEK_OF_MONTH_FIELD: + case UDAT_MILLISECONDS_IN_DAY_FIELD: + case UDAT_TIMEZONE_RFC_FIELD: + case UDAT_TIMEZONE_GENERIC_FIELD: + case UDAT_QUARTER_FIELD: + case UDAT_STANDALONE_QUARTER_FIELD: + case UDAT_TIMEZONE_SPECIAL_FIELD: + case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: + case UDAT_TIMEZONE_ISO_FIELD: + case UDAT_TIMEZONE_ISO_LOCAL_FIELD: +#ifndef U_HIDE_INTERNAL_API + case UDAT_RELATED_YEAR_FIELD: +#endif + case UDAT_AM_PM_MIDNIGHT_NOON_FIELD: + case UDAT_FLEXIBLE_DAY_PERIOD_FIELD: +#ifndef U_HIDE_INTERNAL_API + case UDAT_TIME_SEPARATOR_FIELD: +#endif + // These fields are all unsupported. + return nullptr; + +#ifndef U_HIDE_DEPRECATED_API + case UDAT_FIELD_COUNT: + MOZ_ASSERT_UNREACHABLE("format field sentinel value returned by " + "iterator!"); +#endif + } + + MOZ_ASSERT_UNREACHABLE("unenumerated, undocumented format field returned " + "by iterator"); + return nullptr; +} + +static bool +intl_FormatToPartsDateTime(JSContext* cx, UDateFormat* df, ClippedTime x, + MutableHandleValue result) +{ + MOZ_ASSERT(x.isValid()); + + UErrorCode status = U_ZERO_ERROR; + UFieldPositionIterator* fpositer = ufieldpositer_open(&status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + ScopedICUObject toClose(fpositer); + + RootedString overallResult(cx); + overallResult = CallICU(cx, [df, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) { + return udat_formatForFields(df, x.toDouble(), chars, size, fpositer, status); + }); + if (!overallResult) + return false; + + RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx)); + if (!partsArray) + return false; + + if (overallResult->length() == 0) { + // An empty string contains no parts, so avoid extra work below. + result.setObject(*partsArray); + return true; + } + + size_t lastEndIndex = 0; + + uint32_t partIndex = 0; + RootedObject singlePart(cx); + RootedValue partType(cx); + RootedValue val(cx); + + auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) { + singlePart = NewBuiltinClassInstance(cx); + if (!singlePart) + return false; + + partType = StringValue(cx->names().*type); + if (!DefineDataProperty(cx, singlePart, cx->names().type, partType)) + return false; + + JSLinearString* partSubstr = + NewDependentString(cx, overallResult, beginIndex, endIndex - beginIndex); + if (!partSubstr) + return false; + + val = StringValue(partSubstr); + if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) + return false; + + val = ObjectValue(*singlePart); + if (!DefineDataElement(cx, partsArray, partIndex, val)) + return false; + + lastEndIndex = endIndex; + partIndex++; + return true; + }; + + int32_t fieldInt, beginIndexInt, endIndexInt; + while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt, &endIndexInt)) >= 0) { + MOZ_ASSERT(beginIndexInt >= 0); + MOZ_ASSERT(endIndexInt >= 0); + MOZ_ASSERT(beginIndexInt <= endIndexInt, + "field iterator returning invalid range"); + + size_t beginIndex(beginIndexInt); + size_t endIndex(endIndexInt); + + // Technically this isn't guaranteed. But it appears true in pratice, + // and http://bugs.icu-project.org/trac/ticket/12024 is expected to + // correct the documentation lapse. + MOZ_ASSERT(lastEndIndex <= beginIndex, + "field iteration didn't return fields in order start to " + "finish as expected"); + + if (FieldType type = GetFieldTypeForFormatField(static_cast(fieldInt))) { + if (lastEndIndex < beginIndex) { + if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) + return false; + } + + if (!AppendPart(type, beginIndex, endIndex)) + return false; + } + } + + // Append any final literal. + if (lastEndIndex < overallResult->length()) { + if (!AppendPart(&JSAtomState::literal, lastEndIndex, overallResult->length())) + return false; + } + + result.setObject(*partsArray); + return true; +} + +bool +js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 3); + MOZ_ASSERT(args[0].isObject()); + MOZ_ASSERT(args[1].isNumber()); + MOZ_ASSERT(args[2].isBoolean()); + + Rooted dateTimeFormat(cx); + dateTimeFormat = &args[0].toObject().as(); + + ClippedTime x = TimeClip(args[1].toNumber()); + if (!x.isValid()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE); + return false; + } + + // Obtain a cached UDateFormat object. + void* priv = + dateTimeFormat->getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT).toPrivate(); + UDateFormat* df = static_cast(priv); + if (!df) { + df = NewUDateFormat(cx, dateTimeFormat); + if (!df) + return false; + dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(df)); + } + + // Use the UDateFormat to actually format the time stamp. + return args[2].toBoolean() + ? intl_FormatToPartsDateTime(cx, df, x, args.rval()) + : intl_FormatDateTime(cx, df, x, args.rval()); +} diff --git a/js/src/builtin/intl/DateTimeFormat.h b/js/src/builtin/intl/DateTimeFormat.h new file mode 100644 index 000000000000..41c8564eeb13 --- /dev/null +++ b/js/src/builtin/intl/DateTimeFormat.h @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef builtin_intl_DateTimeFormat_h +#define builtin_intl_DateTimeFormat_h + +#include "mozilla/Attributes.h" + +#include "builtin/intl/CommonFunctions.h" +#include "builtin/SelfHostingDefines.h" +#include "js/Class.h" +#include "js/RootingAPI.h" +#include "vm/NativeObject.h" + +namespace js { + +class FreeOp; +class GlobalObject; + +class DateTimeFormatObject : public NativeObject +{ + public: + static const Class class_; + + static constexpr uint32_t INTERNALS_SLOT = 0; + static constexpr uint32_t UDATE_FORMAT_SLOT = 1; + static constexpr uint32_t SLOT_COUNT = 2; + + static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT, + "INTERNALS_SLOT must match self-hosting define for internals object slot"); + + private: + static const ClassOps classOps_; + + static void finalize(FreeOp* fop, JSObject* obj); +}; + +extern JSObject* +CreateDateTimeFormatPrototype(JSContext* cx, JS::Handle Intl, + JS::Handle global, + JS::MutableHandle constructor, + intl::DateTimeFormatOptions dtfOptions); + +/** + * Returns a new instance of the standard built-in DateTimeFormat constructor. + * Self-hosted code cannot cache this constructor (as it does for others in + * Utilities.js) because it is initialized after self-hosted code is compiled. + * + * Usage: dateTimeFormat = intl_DateTimeFormat(locales, options) + */ +extern MOZ_MUST_USE bool +intl_DateTimeFormat(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * Returns an object indicating the supported locales for date and time + * formatting by having a true-valued property for each such locale with the + * canonicalized language tag as the property name. The object has no + * prototype. + * + * Usage: availableLocales = intl_DateTimeFormat_availableLocales() + */ +extern MOZ_MUST_USE bool +intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * Returns an array with the calendar type identifiers per Unicode + * Technical Standard 35, Unicode Locale Data Markup Language, for the + * supported calendars for the given locale. The default calendar is + * element 0. + * + * Usage: calendars = intl_availableCalendars(locale) + */ +extern MOZ_MUST_USE bool +intl_availableCalendars(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * Returns the calendar type identifier per Unicode Technical Standard 35, + * Unicode Locale Data Markup Language, for the default calendar for the given + * locale. + * + * Usage: calendar = intl_defaultCalendar(locale) + */ +extern MOZ_MUST_USE bool +intl_defaultCalendar(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * 6.4.1 IsValidTimeZoneName ( timeZone ) + * + * Verifies that the given string is a valid time zone name. If it is a valid + * time zone name, its IANA time zone name is returned. Otherwise returns null. + * + * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3 + * + * Usage: ianaTimeZone = intl_IsValidTimeZoneName(timeZone) + */ +extern MOZ_MUST_USE bool +intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * Return the canonicalized time zone name. Canonicalization resolves link + * names to their target time zones. + * + * Usage: ianaTimeZone = intl_canonicalizeTimeZone(timeZone) + */ +extern MOZ_MUST_USE bool +intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * Return the default time zone name. The time zone name is not canonicalized. + * + * Usage: icuDefaultTimeZone = intl_defaultTimeZone() + */ +extern MOZ_MUST_USE bool +intl_defaultTimeZone(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * Return the raw offset from GMT in milliseconds for the default time zone. + * + * Usage: defaultTimeZoneOffset = intl_defaultTimeZoneOffset() + */ +extern MOZ_MUST_USE bool +intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * Return true if the given string is the default time zone as returned by + * intl_defaultTimeZone(). Otherwise return false. + * + * Usage: isIcuDefaultTimeZone = intl_isDefaultTimeZone(icuDefaultTimeZone) + */ +extern MOZ_MUST_USE bool +intl_isDefaultTimeZone(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * Return a pattern in the date-time format pattern language of Unicode + * Technical Standard 35, Unicode Locale Data Markup Language, for the + * best-fit date-time format pattern corresponding to skeleton for the + * given locale. + * + * Usage: pattern = intl_patternForSkeleton(locale, skeleton) + */ +extern MOZ_MUST_USE bool +intl_patternForSkeleton(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * Return a pattern in the date-time format pattern language of Unicode + * Technical Standard 35, Unicode Locale Data Markup Language, for the + * best-fit date-time style for the given locale. + * The function takes four arguments: + * + * locale + * BCP47 compliant locale string + * dateStyle + * A string with values: full or long or medium or short, or `undefined` + * timeStyle + * A string with values: full or long or medium or short, or `undefined` + * timeZone + * IANA time zone name + * + * Date and time style categories map to CLDR time/date standard + * format patterns. + * + * For the definition of a pattern string, see LDML 4.8: + * http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns + * + * If `undefined` is passed to `dateStyle` or `timeStyle`, the respective + * portions of the pattern will not be included in the result. + * + * Usage: pattern = intl_patternForStyle(locale, dateStyle, timeStyle, timeZone) + */ +extern MOZ_MUST_USE bool +intl_patternForStyle(JSContext* cx, unsigned argc, JS::Value* vp); + +/** + * Returns a String value representing x (which must be a Number value) + * according to the effective locale and the formatting options of the + * given DateTimeFormat. + * + * Spec: ECMAScript Internationalization API Specification, 12.3.2. + * + * Usage: formatted = intl_FormatDateTime(dateTimeFormat, x, formatToParts) + */ +extern MOZ_MUST_USE bool +intl_FormatDateTime(JSContext* cx, unsigned argc, JS::Value* vp); + +} // namespace js + +#endif /* builtin_intl_DateTimeFormat_h */ diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index afcd0783032f..214ad4420e28 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -13,6 +13,7 @@ #include "builtin/AtomicsObject.h" #include "builtin/Intl.h" #include "builtin/intl/Collator.h" +#include "builtin/intl/DateTimeFormat.h" #include "builtin/intl/NumberFormat.h" #include "builtin/MapObject.h" #include "builtin/SIMD.h" diff --git a/js/src/moz.build b/js/src/moz.build index b6a3a52de1f4..fdc26a6b6881 100755 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -162,6 +162,7 @@ UNIFIED_SOURCES += [ 'builtin/Intl.cpp', 'builtin/intl/Collator.cpp', 'builtin/intl/CommonFunctions.cpp', + 'builtin/intl/DateTimeFormat.cpp', 'builtin/intl/NumberFormat.cpp', 'builtin/intl/SharedIntlData.cpp', 'builtin/MapObject.cpp', diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index e8a2f88eb2cb..c59e0ef06a36 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -25,6 +25,7 @@ #include "builtin/Intl.h" #include "builtin/intl/Collator.h" +#include "builtin/intl/DateTimeFormat.h" #include "builtin/intl/NumberFormat.h" #include "builtin/MapObject.h" #include "builtin/ModuleObject.h"