Added support for ES5 date time string format to Date.parse.
authorricow@chromium.org <ricow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 3 May 2010 06:43:25 +0000 (06:43 +0000)
committerricow@chromium.org <ricow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 3 May 2010 06:43:25 +0000 (06:43 +0000)
Review URL: http://codereview.chromium.org/1704016

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4557 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/date.js
src/dateparser-inl.h
src/dateparser.cc
src/dateparser.h
test/mjsunit/date-parse.js

index 216d5df..b9e19d6 100644 (file)
@@ -620,7 +620,7 @@ function DatePrintString(time) {
 // -------------------------------------------------------------------
 
 // Reused output buffer. Used when parsing date strings.
-var parse_buffer = $Array(7);
+var parse_buffer = $Array(8);
 
 // ECMA 262 - 15.9.4.2
 function DateParse(string) {
@@ -628,13 +628,13 @@ function DateParse(string) {
   if (IS_NULL(arr)) return $NaN;
 
   var day = MakeDay(arr[0], arr[1], arr[2]);
-  var time = MakeTime(arr[3], arr[4], arr[5], 0);
+  var time = MakeTime(arr[3], arr[4], arr[5], arr[6]);
   var date = MakeDate(day, time);
 
-  if (IS_NULL(arr[6])) {
+  if (IS_NULL(arr[7])) {
     return TimeClip(UTC(date));
   } else {
-    return TimeClip(date - arr[6] * 1000);
+    return TimeClip(date - arr[7] * 1000);
   }
 }
 
index d5921d5..be353a3 100644 (file)
@@ -54,16 +54,25 @@ bool DateParser::Parse(Vector<Char> str, FixedArray* out) {
         } else {
           // n + ":"
           if (!time.Add(n)) return false;
+          in.Skip('.');
         }
+      } else if (in.Skip('.') && time.IsExpecting(n)) {
+        time.Add(n);
+        if (!in.IsAsciiDigit()) return false;
+        int n = in.ReadUnsignedNumber();
+        time.AddFinal(n);
       } else if (tz.IsExpecting(n)) {
         tz.SetAbsoluteMinute(n);
       } else if (time.IsExpecting(n)) {
         time.AddFinal(n);
-        // Require end or white space immediately after finalizing time.
-        if (!in.IsEnd() && !in.SkipWhiteSpace()) return false;
+        // Require end, white space or Z immediately after finalizing time.
+        if (!in.IsEnd() && !in.SkipWhiteSpace() && !in.Is('Z')) return false;
       } else {
         if (!day.Add(n)) return false;
         in.Skip('-');  // Ignore suffix '-' for year, month, or day.
+        // Skip trailing 'T' for ECMAScript 5 date string format but make
+        // sure that it is followed by a digit (for the time).
+        if (in.Skip('T') && !in.IsAsciiDigit()) return false;
       }
     } else if (in.IsAsciiAlphaOrAbove()) {
       // Parse a "word" (sequence of chars. >= 'A').
index 51a63e1..e68532f 100644 (file)
@@ -33,6 +33,16 @@ namespace v8 {
 namespace internal {
 
 bool DateParser::DayComposer::Write(FixedArray* output) {
+  // Set year to 0 by default.
+  if (index_ < 1) {
+    comp_[index_++] = 1;
+  }
+
+  // Day and month defaults to 1.
+  while (index_ < kSize) {
+      comp_[index_++] = 1;
+  }
+
   int year = 0;  // Default year is 0 (=> 2000) for KJS compatibility.
   int month = kNone;
   int day = kNone;
@@ -88,6 +98,7 @@ bool DateParser::TimeComposer::Write(FixedArray* output) {
   int& hour = comp_[0];
   int& minute = comp_[1];
   int& second = comp_[2];
+  int& millisecond = comp_[3];
 
   if (hour_offset_ != kNone) {
     if (!IsHour12(hour)) return false;
@@ -95,11 +106,13 @@ bool DateParser::TimeComposer::Write(FixedArray* output) {
     hour += hour_offset_;
   }
 
-  if (!IsHour(hour) || !IsMinute(minute) || !IsSecond(second)) return false;
+  if (!IsHour(hour) || !IsMinute(minute) ||
+      !IsSecond(second) || !IsMillisecond(millisecond)) return false;
 
   output->set(HOUR, Smi::FromInt(hour));
   output->set(MINUTE, Smi::FromInt(minute));
   output->set(SECOND, Smi::FromInt(second));
+  output->set(MILLISECOND, Smi::FromInt(millisecond));
   return true;
 }
 
@@ -134,6 +147,7 @@ const int8_t DateParser::KeywordTable::
   {'p', 'm', '\0', DateParser::AM_PM, 12},
   {'u', 't', '\0', DateParser::TIME_ZONE_NAME, 0},
   {'u', 't', 'c', DateParser::TIME_ZONE_NAME, 0},
+  {'z', '\0', '\0', DateParser::TIME_ZONE_NAME, 0},
   {'g', 'm', 't', DateParser::TIME_ZONE_NAME, 0},
   {'c', 'd', 't', DateParser::TIME_ZONE_NAME, -5},
   {'c', 's', 't', DateParser::TIME_ZONE_NAME, -6},
index d339a4f..d999d9c 100644 (file)
@@ -44,13 +44,14 @@ class DateParser : public AllStatic {
   // [3]: hour
   // [4]: minute
   // [5]: second
-  // [6]: UTC offset in seconds, or null value if no timezone specified
+  // [6]: millisecond
+  // [7]: UTC offset in seconds, or null value if no timezone specified
   // If parsing fails, return false (content of output array is not defined).
   template <typename Char>
   static bool Parse(Vector<Char> str, FixedArray* output);
 
   enum {
-    YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, UTC_OFFSET, OUTPUT_SIZE
+    YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND, UTC_OFFSET, OUTPUT_SIZE
   };
 
  private:
@@ -189,7 +190,9 @@ class DateParser : public AllStatic {
     TimeComposer() : index_(0), hour_offset_(kNone) {}
     bool IsEmpty() const { return index_ == 0; }
     bool IsExpecting(int n) const {
-      return (index_ == 1 && IsMinute(n)) || (index_ == 2 && IsSecond(n));
+      return (index_ == 1 && IsMinute(n)) ||
+             (index_ == 2 && IsSecond(n)) ||
+             (index_ == 3 && IsMillisecond(n));
     }
     bool Add(int n) {
       return index_ < kSize ? (comp_[index_++] = n, true) : false;
@@ -207,8 +210,9 @@ class DateParser : public AllStatic {
     static bool IsHour(int x) { return Between(x, 0, 23); }
     static bool IsHour12(int x) { return Between(x, 0, 12); }
     static bool IsSecond(int x) { return Between(x, 0, 59); }
+    static bool IsMillisecond(int x) { return Between(x, 0, 999); }
 
-    static const int kSize = 3;
+    static const int kSize = 4;
     int comp_[kSize];
     int index_;
     int hour_offset_;
index 4bbb2c6..23a6993 100644 (file)
@@ -205,7 +205,6 @@ var testCasesPDT = [
     'Saturday, 01-Jan-00 01:00:00 PDT',
     '01 Jan 00 01:00 -0700'];
 
-
 // Local time cases.
 var testCasesLocalTime = [
     // Allow timezone ommision.
@@ -233,6 +232,27 @@ var testCasesMisc = [
     ['Saturday, 01-Jan-00 08:00 PM UT', 946756800000],
     ['01 Jan 00 08:00 PM +0000', 946756800000]];
 
+// Test different version of the ES5 date time string format.
+var testCasesES5Misc = [
+    ['2000-01-01T08:00:00.000Z', 946713600000],
+    ['2000-01-01T08:00:00Z', 946713600000],
+    ['2000-01-01T08:00Z', 946713600000],
+    ['2000-01T08:00:00.000Z', 946713600000],
+    ['2000T08:00:00.000Z', 946713600000],
+    ['2000T08:00Z', 946713600000],
+    ['2000-01T00:00:00.000-08:00', 946713600000],
+    ['2000-01T08:00:00.001Z', 946713600001],
+    ['2000-01T08:00:00.099Z', 946713600099],
+    ['2000-01T08:00:00.999Z', 946713600999],
+    ['2000-01T00:00:00.001-08:00', 946713600001]];
+
+var testCasesES5MiscNegative = [
+    '2000-01-01TZ',
+    '2000-01-01T60Z',
+    '2000-01-01T60:60Z',
+    '2000-01-0108:00Z',
+    '2000-01-01T08Z'];
+
 
 // Run all the tests.
 testCasesUT.forEach(testDateParse);
@@ -248,6 +268,12 @@ testCasesPDT.forEach(testDateParse);
 testCasesLocalTime.forEach(testDateParseLocalTime);
 testCasesMisc.forEach(testDateParseMisc);
 
+// ES5 date time string format compliance.
+testCasesES5Misc.forEach(testDateParseMisc);
+testCasesES5MiscNegative.forEach(function (s) {
+    assertTrue(isNaN(Date.parse(s)), s + " is not NaN.");
+});
+
 
 // Test that we can parse our own date format.
 // (Dates from 1970 to ~2070 with 150h steps.)