Fix Date constructor and add Date.prototype.toISOString
authorLars Knoll <lars.knoll@digia.com>
Mon, 21 Jan 2013 10:39:08 +0000 (11:39 +0100)
committerSimon Hausmann <simon.hausmann@digia.com>
Mon, 21 Jan 2013 11:23:21 +0000 (12:23 +0100)
Change-Id: Ib905639ed903cccdeb2649374f744a1fc64f3c1c
Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
qv4ecmaobjects.cpp
qv4ecmaobjects_p.h
tests/TestExpectations

index c3bd81c..df3ff9b 100644 (file)
@@ -366,6 +366,144 @@ static inline double FromDateTime(const QDateTime &dt)
 
 static inline double ParseString(const QString &s)
 {
+    // first try the format defined in 15.9.1.15, only if that fails fall back to
+    // QDateTime for parsing
+
+    // the define string format is YYYY-MM-DDTHH:mm:ss.sssZ
+    // It can be date or time only, and the second and later components
+    // of both fields are optional
+    // and extended syntax for negative and large positive years exists: +/-YYYYYY
+
+    enum Format {
+        Year,
+        Month,
+        Day,
+        Hour,
+        Minute,
+        Second,
+        MilliSecond,
+        TimezoneHour,
+        TimezoneMinute,
+        Done
+    };
+
+    const QChar *ch = s.constData();
+    const QChar *end = ch + s.length();
+
+    uint format = Year;
+    int current = 0;
+    int currentSize = 0;
+    bool extendedYear = false;
+
+    int yearSign = 1;
+    int year = 0;
+    int month = 0;
+    int day = 1;
+    int hour = 0;
+    int minute = 0;
+    int second = 0;
+    int msec = 0;
+    int offsetSign = 1;
+    int offset = 0;
+
+    bool error = false;
+    if (*ch == '+' || *ch == '-') {
+        extendedYear = true;
+        if (*ch == '-')
+            yearSign = -1;
+        ++ch;
+    }
+    while (ch <= end) {
+        if (*ch >= '0' && *ch <= '9') {
+            current *= 10;
+            current += ch->unicode() - '0';
+            ++currentSize;
+        } else { // other char, delimits field
+            switch (format) {
+            case Year:
+                year = current;
+                if (extendedYear)
+                    error = (currentSize != 6);
+                else
+                    error = (currentSize != 4);
+                break;
+            case Month:
+                month = current - 1;
+                error = (currentSize != 2) || month > 11;
+                break;
+            case Day:
+                day = current;
+                error = (currentSize != 2) || day > 31;
+                break;
+            case Hour:
+                hour = current;
+                error = (currentSize != 2) || hour > 24;
+                break;
+            case Minute:
+                minute = current;
+                error = (currentSize != 2) || minute > 60;
+                break;
+            case Second:
+                second = current;
+                error = (currentSize != 2) || second > 60;
+                break;
+            case MilliSecond:
+                msec = current;
+                error = (currentSize != 3);
+                break;
+            case TimezoneHour:
+                offset = current*60;
+                error = (currentSize != 2) || offset > 23*60;
+                break;
+            case TimezoneMinute:
+                offset += current;
+                error = (currentSize != 2) || current >= 60;
+                break;
+            }
+            if (*ch == 'T') {
+                if (format >= Hour)
+                    error = true;
+                format = Hour;
+            } else if (*ch == '-') {
+                if (format < Day)
+                    ++format;
+                else if (format < Minute)
+                    error = true;
+                else if (format >= TimezoneHour)
+                    error = true;
+                else {
+                    offsetSign = -1;
+                    format = TimezoneHour;
+                }
+            } else if (*ch == ':') {
+                if (format != Hour && format != Minute && format != TimezoneHour)
+                    error = true;
+                ++format;
+            } else if (*ch == '.') {
+                if (format != Second)
+                    error = true;
+                ++format;
+            } else if (*ch == '+') {
+                if (format < Minute || format >= TimezoneHour)
+                    error = true;
+                format = TimezoneHour;
+            } else if (*ch == 'Z' || *ch == 0) {
+                format = Done;
+            }
+            current = 0;
+            currentSize = 0;
+        }
+        if (error || format == Done)
+            break;
+        ++ch;
+    }
+
+    if (!error) {
+        double t = MakeDate(MakeDay(year * yearSign, month, day), MakeTime(hour, minute, second, msec));
+        t += offset * offsetSign * 60 * 1000;
+        return t;
+    }
+
     QDateTime dt = QDateTime::fromString(s, Qt::TextDate);
     if (!dt.isValid())
         dt = QDateTime::fromString(s, Qt::ISODate);
@@ -2433,7 +2571,7 @@ Value DateCtor::construct(ExecutionContext *ctx)
             arg = __qmljs_to_primitive(arg, ctx, PREFERREDTYPE_HINT);
 
         if (arg.isString())
-            t = ParseString(arg.toString(ctx)->toQString());
+            t = ParseString(arg.stringValue()->toQString());
         else
             t = TimeClip(arg.toNumber(ctx));
     }
@@ -2516,6 +2654,7 @@ void DatePrototype::init(ExecutionContext *ctx, const Value &ctor)
     defineDefaultProperty(ctx, QStringLiteral("setUTCFullYear"), method_setUTCFullYear, 3);
     defineDefaultProperty(ctx, QStringLiteral("toUTCString"), method_toUTCString, 0);
     defineDefaultProperty(ctx, QStringLiteral("toGMTString"), method_toUTCString, 0);
+    defineDefaultProperty(ctx, QStringLiteral("toISOString"), method_toISOString, 0);
 }
 
 double DatePrototype::getThisDate(ExecutionContext *ctx)
@@ -2528,24 +2667,6 @@ double DatePrototype::getThisDate(ExecutionContext *ctx)
     }
 }
 
-Value DatePrototype::method_MakeTime(ExecutionContext *ctx)
-{
-    ctx->throwUnimplemented(QStringLiteral("Data.MakeTime"));
-    return Value::undefinedValue();
-}
-
-Value DatePrototype::method_MakeDate(ExecutionContext *ctx)
-{
-    ctx->throwUnimplemented(QStringLiteral("Data.MakeDate"));
-    return Value::undefinedValue();
-}
-
-Value DatePrototype::method_TimeClip(ExecutionContext *ctx)
-{
-    ctx->throwUnimplemented(QStringLiteral("Data.TimeClip"));
-    return Value::undefinedValue();
-}
-
 Value DatePrototype::method_parse(ExecutionContext *ctx)
 {
     return Value::fromDouble(ParseString(ctx->argument(0).toString(ctx)->toQString()));
@@ -3007,6 +3128,57 @@ Value DatePrototype::method_toUTCString(ExecutionContext *ctx)
     return Value::fromString(ctx, ToUTCString(t));
 }
 
+static void addZeroPrefixedInt(QString &str, int num, int nDigits)
+{
+    str.resize(str.size() + nDigits);
+
+    QChar *c = str.data() + str.size() - 1;
+    while (nDigits) {
+        *c = QChar(num % 10 + '0');
+        num /= 10;
+        --c;
+        --nDigits;
+    }
+}
+
+Value DatePrototype::method_toISOString(ExecutionContext *ctx)
+{
+    DateObject *self = ctx->thisObject.asDateObject();
+    if (!self)
+        ctx->throwTypeError();
+
+    double t = self->value.asDouble();
+    if (!std::isfinite(t))
+        ctx->throwRangeError(ctx->thisObject);
+
+    QString result;
+    int year = (int)YearFromTime(t);
+    if (year < 0 || year > 9999) {
+        if (qAbs(year) >= 1000000)
+            return Value::fromString(ctx, QStringLiteral("Invalid Date"));
+        result += year < 0 ? '-' : '+';
+        year = qAbs(year);
+        addZeroPrefixedInt(result, year, 6);
+    } else {
+        addZeroPrefixedInt(result, year, 4);
+    }
+    result += '-';
+    addZeroPrefixedInt(result, (int)MonthFromTime(t) + 1, 2);
+    result += '-';
+    addZeroPrefixedInt(result, (int)DateFromTime(t), 2);
+    result += 'T';
+    addZeroPrefixedInt(result, HourFromTime(t), 2);
+    result += ':';
+    addZeroPrefixedInt(result, MinFromTime(t), 2);
+    result += ':';
+    addZeroPrefixedInt(result, SecFromTime(t), 2);
+    result += '.';
+    addZeroPrefixedInt(result, msFromTime(t), 3);
+    result += 'Z';
+
+    return Value::fromString(ctx, result);
+}
+
 //
 // RegExp object
 //
index ffaf803..571dc17 100644 (file)
@@ -235,11 +235,9 @@ struct DatePrototype: DateObject
 
     static double getThisDate(ExecutionContext *ctx);
 
-    static Value method_MakeTime(ExecutionContext *ctx);
-    static Value method_MakeDate(ExecutionContext *ctx);
-    static Value method_TimeClip(ExecutionContext *ctx);
     static Value method_parse(ExecutionContext *ctx);
     static Value method_UTC(ExecutionContext *ctx);
+
     static Value method_toString(ExecutionContext *ctx);
     static Value method_toDateString(ExecutionContext *ctx);
     static Value method_toTimeString(ExecutionContext *ctx);
@@ -283,6 +281,7 @@ struct DatePrototype: DateObject
     static Value method_setFullYear(ExecutionContext *ctx);
     static Value method_setUTCFullYear(ExecutionContext *ctx);
     static Value method_toUTCString(ExecutionContext *ctx);
+    static Value method_toISOString(ExecutionContext *ctx);
 };
 
 struct RegExpCtor: FunctionObject
index 6b3a232..39c0a49 100644 (file)
@@ -524,7 +524,6 @@ S15.1.3.2_A5.3 failing
 15.2.3.3-4-11 failing
 15.2.3.3-4-12 failing
 15.2.3.3-4-13 failing
-15.2.3.3-4-161 failing
 15.2.3.3-4-162 failing
 15.2.3.3-4-164 failing
 15.2.3.3-4-51 failing
@@ -547,7 +546,6 @@ S15.1.3.2_A5.3 failing
 15.2.3.6-4-360-7 failing
 15.2.3.6-4-621 failing
 15.2.3.6-4-622 failing
-15.2.3.6-4-623 failing
 15.2.3.6-4-624 failing
 15.2.3.7-6-a-110 failing
 15.2.3.7-6-a-280 failing
@@ -960,7 +958,6 @@ S15.7.4.5_A1.4_T01 failing
 15.9.4.4-0-2 failing
 15.9.4.4-0-3 failing
 15.9.4.4-0-4 failing
-15.9.1.15-1 failing
 S15.9.2.1_A2 failing
 S15.9.3.1_A5_T1 failing
 S15.9.3.1_A5_T2 failing
@@ -1090,18 +1087,6 @@ S15.9.5.1_A2_T1 failing
 13.3.0_2 failing
 13.3.0_6 failing
 13.3.0_7 failing
-15.9.5.43-0-10 failing
-15.9.5.43-0-11 failing
-15.9.5.43-0-12 failing
-15.9.5.43-0-13 failing
-15.9.5.43-0-14 failing
-15.9.5.43-0-15 failing
-15.9.5.43-0-2 failing
-15.9.5.43-0-3 failing
-15.9.5.43-0-4 failing
-15.9.5.43-0-5 failing
-15.9.5.43-0-8 failing
-15.9.5.43-0-9 failing
 15.9.5.44-0-1 failing
 15.9.5.44-0-2 failing
 6.2.2_a failing