Bug 837957 - Implement ICU dependent functions of Intl.Collator, Intl.NumberFormat, Intl.DateTimeFormat (part 7). r=jwalden
authorNorbert Lindenberg <mozilladev@lindenbergsoftware.com>
Tue, 19 Mar 2013 12:04:07 -0700
changeset 125504 41789248e1e6d91f56ff8666ea85b5ff3d98d5be
parent 125503 19d4df9405dd1c22ffc23c47abfcaea926ac4a84
child 125505 42caf484567704e1cee9918c63e37afd7d7a41c4
push id24459
push useremorley@mozilla.com
push dateWed, 20 Mar 2013 11:46:36 +0000
treeherdermozilla-central@1d6fe70c79c5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs837957
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 837957 - Implement ICU dependent functions of Intl.Collator, Intl.NumberFormat, Intl.DateTimeFormat (part 7). r=jwalden
js/src/builtin/Intl.cpp
js/src/builtin/Intl.h
js/src/vm/CommonPropertyNames.h
js/src/vm/DateTime.h
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -13,16 +13,17 @@
 
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jsinterp.h"
 #include "jsobj.h"
 
 #include "builtin/Intl.h"
+#include "vm/DateTime.h"
 #include "vm/GlobalObject.h"
 #include "vm/Stack.h"
 #include "vm/StringBuffer.h"
 
 #if ENABLE_INTL_API
 #include "unicode/locid.h"
 #include "unicode/numsys.h"
 #include "unicode/ucal.h"
@@ -1406,19 +1407,19 @@ intl_FormatNumber(JSContext *cx, UNumber
     if (MOZ_DOUBLE_IS_NEGATIVE_ZERO(x))
         x = 0.0;
 
     StringBuffer chars(cx);
     if (!chars.resize(INITIAL_STRING_BUFFER_SIZE))
         return false;
     UErrorCode status = U_ZERO_ERROR;
     int size = unum_formatDouble(nf, x, chars.begin(), INITIAL_STRING_BUFFER_SIZE, NULL, &status);
-    if (!chars.resize(size))
-        return false;
     if (status == U_BUFFER_OVERFLOW_ERROR) {
+        if (!chars.resize(size))
+            return false;
         status = U_ZERO_ERROR;
         unum_formatDouble(nf, x, chars.begin(), size, NULL, &status);
     }
     if (U_FAILURE(status)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
 
@@ -1782,16 +1783,167 @@ js::intl_patternForSkeleton(JSContext *c
 
     RootedString str(cx, JS_NewUCStringCopyZ(cx, pattern));
     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, HandleObject dateTimeFormat)
+{
+    RootedValue value(cx);
+
+    RootedObject internals(cx);
+    if (!GetInternals(cx, dateTimeFormat, &internals))
+       return NULL;
+
+    if (!JSObject::getProperty(cx, internals, internals, cx->names().locale, &value)) {
+        return NULL;
+    }
+    JSAutoByteString locale(cx, value.toString());
+    if (!locale)
+        return NULL;
+
+    // UDateFormat options with default values.
+    const UChar *uTimeZone = NULL;
+    uint32_t uTimeZoneLength = 0;
+    const UChar *uPattern = NULL;
+    uint32_t uPatternLength = 0;
+
+    SkipRoot skipTimeZone(cx, &uTimeZone);
+    SkipRoot skipPattern(cx, &uPattern);
+
+    // 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.
+
+    RootedId id(cx, NameToId(cx->names().timeZone));
+    bool hasP;
+    if (!JSObject::hasProperty(cx, internals, id, &hasP))
+        return NULL;
+    if (hasP) {
+        if (!JSObject::getProperty(cx, internals, internals, cx->names().timeZone, &value))
+            return NULL;
+        if (!value.isUndefined()) {
+            uTimeZone = JS_GetStringCharsZ(cx, value.toString());
+            if (!uTimeZone)
+                return NULL;
+            uTimeZoneLength = u_strlen(uTimeZone);
+        }
+    }
+    if (!JSObject::getProperty(cx, internals, internals, cx->names().pattern, &value))
+        return NULL;
+    uPattern = JS_GetStringCharsZ(cx, value.toString());
+    if (!uPattern)
+        return NULL;
+    uPatternLength = u_strlen(uPattern);
+
+    UErrorCode status = U_ZERO_ERROR;
+
+    // If building with ICU headers before 50.1, use UDAT_IGNORE instead of
+    // UDAT_PATTERN.
+    UDateFormat *df =
+        udat_open(UDAT_PATTERN, UDAT_PATTERN, icuLocale(locale.ptr()), uTimeZone, uTimeZoneLength,
+                  uPattern, uPatternLength, &status);
+    if (U_FAILURE(status)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INTERNAL_INTL_ERROR);
+        return NULL;
+    }
+
+    // ECMAScript requires the Gregorian calendar to be used from the beginning
+    // of ECMAScript time.
+    UCalendar *cal = (UCalendar *) 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, double x, MutableHandleValue result)
+{
+    if (!MOZ_DOUBLE_IS_FINITE(x)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DATE_NOT_FINITE);
+        return false;
+    }
+
+    StringBuffer chars(cx);
+    if (!chars.resize(INITIAL_STRING_BUFFER_SIZE))
+        return false;
+    UErrorCode status = U_ZERO_ERROR;
+    int size = udat_format(df, x, chars.begin(), INITIAL_STRING_BUFFER_SIZE, NULL, &status);
+    if (status == U_BUFFER_OVERFLOW_ERROR) {
+        if (!chars.resize(size))
+            return false;
+        status = U_ZERO_ERROR;
+        udat_format(df, x, chars.begin(), size, NULL, &status);
+    }
+    if (U_FAILURE(status)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INTERNAL_INTL_ERROR);
+        return false;
+    }
+
+    RootedString str(cx, chars.finishString());
+    if (!str)
+        return false;
+
+    result.setString(str);
+    return true;
+}
+
+JSBool
+js::intl_FormatDateTime(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    JS_ASSERT(args.length() == 2);
+    JS_ASSERT(args[0].isObject());
+    JS_ASSERT(args[1].isNumber());
+
+    RootedObject dateTimeFormat(cx, &args[0].toObject());
+
+    // Obtain a UDateFormat object, cached if possible.
+    bool isDateTimeFormatInstance = dateTimeFormat->getClass() == &DateTimeFormatClass;
+    UDateFormat *df;
+    if (isDateTimeFormatInstance) {
+        df = static_cast<UDateFormat*>(dateTimeFormat->getReservedSlot(UDATE_FORMAT_SLOT).toPrivate());
+        if (!df) {
+            df = NewUDateFormat(cx, dateTimeFormat);
+            if (!df)
+                return false;
+            dateTimeFormat->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(df));
+        }
+    } else {
+        // There's no good place to cache the ICU date-time format for an object
+        // that has been initialized as a DateTimeFormat but is not a
+        // DateTimeFormat instance. One possibility might be to add a
+        // DateTimeFormat instance as an internal property to each such object.
+        df = NewUDateFormat(cx, dateTimeFormat);
+        if (!df)
+            return false;
+    }
+
+    // Use the UDateFormat to actually format the time stamp.
+    RootedValue result(cx);
+    bool success = intl_FormatDateTime(cx, df, args[1].toNumber(), &result);
+
+    if (!isDateTimeFormatInstance)
+        udat_close(df);
+    if (!success)
+        return false;
+    args.rval().set(result);
+    return true;
+}
+
 
 /******************** Intl ********************/
 
 Class js::IntlClass = {
     js_Object_str,
     JSCLASS_HAS_CACHED_PROTO(JSProto_Intl),
     JS_PropertyStub,         /* addProperty */
     JS_PropertyStub,         /* delProperty */
@@ -1833,17 +1985,18 @@ js_InitIntlClass(JSContext *cx, HandleOb
     // object reserves slots to track standard built-in objects, but doesn't
     // normally keep references to non-constructors. This makes sure there is one.
     RootedObject Intl(cx, global->getOrCreateIntlObject(cx));
     if (!Intl)
         return NULL;
 
     RootedValue IntlValue(cx, ObjectValue(*Intl));
     if (!JSObject::defineProperty(cx, global, cx->names().Intl, IntlValue,
-                                  JS_PropertyStub, JS_StrictPropertyStub, 0)) {
+                                  JS_PropertyStub, JS_StrictPropertyStub, 0))
+    {
         return NULL;
     }
 
     if (!JS_DefineFunctions(cx, Intl, intl_static_methods))
         return NULL;
 
     // Skip initialization of the Intl constructors during initialization of the
     // self-hosting global as we may get here before self-hosted code is compiled,
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -135,11 +135,23 @@ intl_availableCalendars(JSContext *cx, u
  * best-fit date-time format pattern corresponding to skeleton for the
  * given locale.
  *
  * Usage: pattern = intl_patternForSkeleton(locale, skeleton)
  */
 extern JSBool
 intl_patternForSkeleton(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)
+ */
+extern JSBool
+intl_FormatDateTime(JSContext *cx, unsigned argc, Value *vp);
+
 } // namespace js
 
 #endif /* Intl_h___ */
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -108,28 +108,30 @@
     macro(NumberFormatFormatGet, NumberFormatFormatGet, "Intl_NumberFormat_format_get") \
     macro(numeric, numeric, "numeric") \
     macro(objectNull, objectNull, "[object Null]") \
     macro(objectUndefined, objectUndefined, "[object Undefined]") \
     macro(of, of, "of") \
     macro(offset, offset, "offset") \
     macro(parseFloat, parseFloat, "parseFloat") \
     macro(parseInt, parseInt, "parseInt") \
+    macro(pattern, pattern, "pattern") \
     macro(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \
     macro(proto, proto, "__proto__") \
     macro(return, return_, "return") \
     macro(sensitivity, sensitivity, "sensitivity") \
     macro(set, set, "set") \
     macro(shape, shape, "shape") \
     macro(source, source, "source") \
     macro(stack, stack, "stack") \
     macro(sticky, sticky, "sticky") \
     macro(style, style, "style") \
     macro(test, test, "test") \
     macro(throw, throw_, "throw") \
+    macro(timeZone, timeZone, "timeZone") \
     macro(toGMTString, toGMTString, "toGMTString") \
     macro(toISOString, toISOString, "toISOString") \
     macro(toJSON, toJSON, "toJSON") \
     macro(toLocaleString, toLocaleString, "toLocaleString") \
     macro(toSource, toSource, "toSource") \
     macro(toString, toString, "toString") \
     macro(toUTCString, toUTCString, "toUTCString") \
     macro(true, true_, "true") \
--- a/js/src/vm/DateTime.h
+++ b/js/src/vm/DateTime.h
@@ -32,22 +32,26 @@ const double msPerDay = msPerHour * Hour
  * Additional quantities not mentioned in the spec.  Be careful using these!
  * They aren't doubles (and aren't defined in terms of all the other constants
  * so that they can be used in constexpr scenarios; if you need constants that
  * trigger floating point semantics, you'll have to manually cast to get it.
  */
 const unsigned SecondsPerHour = 60 * 60;
 const unsigned SecondsPerDay = SecondsPerHour * 24;
 
+const double StartOfTime = -8.64e15;
+const double EndOfTime = 8.64e15;
+const double MaxTimeMagnitude = 8.64e15;
+
 /* ES5 15.9.1.14. */
 inline double
 TimeClip(double time)
 {
     /* Steps 1-2. */
-    if (!MOZ_DOUBLE_IS_FINITE(time) || mozilla::Abs(time) > 8.64e15)
+    if (!MOZ_DOUBLE_IS_FINITE(time) || mozilla::Abs(time) > MaxTimeMagnitude)
         return js_NaN;
 
     /* Step 3. */
     return ToInteger(time + (+0.0));
 }
 
 /*
  * Stores date/time information, particularly concerning the current local