From 38e5cf2affba95ee60f34321104d23e36b05a617 Mon Sep 17 00:00:00 2001 From: Xiaoyin Liu Date: Sat, 13 Jan 2018 07:04:11 -0800 Subject: [PATCH] Make Date.parse recognize padded and negative years This PR will finally make Date.parse able to parse any output of Date.toString(), Date.toUTCString, and Date.toISOString(). Fixes #4178 Fixes #4300 --- lib/Runtime/Library/DateImplementation.cpp | 28 ++++++++-- test/Date/DateParse2.js | 9 ++- test/Date/DateParse2.v5.baseline | 56 +++++++++++++++++-- test/Date/parseToStringResults.js | 41 ++++++++++++++ .../parseToUTCStringAndToISOStringResults.js | 41 ++++++++++++++ test/Date/rlexe.xml | 14 +++++ 6 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 test/Date/parseToStringResults.js create mode 100644 test/Date/parseToUTCStringAndToISOStringResults.js diff --git a/lib/Runtime/Library/DateImplementation.cpp b/lib/Runtime/Library/DateImplementation.cpp index b5bb8620107..1441af52dfe 100644 --- a/lib/Runtime/Library/DateImplementation.cpp +++ b/lib/Runtime/Library/DateImplementation.cpp @@ -1043,7 +1043,6 @@ namespace Js { char16 *pchBase; char16 *pch; - char16 *pchEndOfDigits = nullptr; char16 ch; char16 *pszSrc = nullptr; @@ -1067,6 +1066,7 @@ namespace Js { int tAmPm = 0; int tBcAd = 0; + size_t numOfDigits = 0; double tv = JavascriptNumber::NaN; // Initialized for error handling. //Create a copy to analyze @@ -1080,6 +1080,7 @@ namespace Js { _wcslwr_s(pszSrc,ulength+1); bool isDateNegativeVersion5 = false; bool isNextFieldDateNegativeVersion5 = false; + bool isZeroPaddedYear = false; const Js::CharClassifier *classifier = scriptContext->GetCharClassifier(); #pragma prefast(suppress: __WARNING_INCORRECT_VALIDATION, "pch is guaranteed to be null terminated by __in_z on psz and js_memcpy_s copying the null byte") for (pch = pszSrc; 0 != (ch = classifier->SkipBiDirectionalChars(pch));) @@ -1235,6 +1236,12 @@ namespace Js { goto LError; } lwMonth = pszs->lwVal; + if ('-' == *pch) + { + // handle the case date is negative for "Thu, 23 Sep -0007 00:00:00 GMT" + isDateNegativeVersion5 = true; + pch++; + } break; } case ParseStringTokenType::Zone: @@ -1269,7 +1276,7 @@ namespace Js { lwT = lwT * 10 + *pch - '0'; } - pchEndOfDigits = pch; + numOfDigits = pch - pchBase; // skip to the next real character while (0 != (ch = *pch) && (ch <= ' ' || classifier->IsBiDirectionalChar(ch))) { @@ -1317,7 +1324,6 @@ namespace Js { { AssertMsg(isNextFieldDateNegativeVersion5 == false, "isNextFieldDateNegativeVersion5 == false"); - size_t numOfDigits = static_cast(pchEndOfDigits - pchBase); if (numOfDigits <= 1) { // 1 digit only, treat it as hundreds @@ -1395,6 +1401,10 @@ namespace Js { AssertMsg(isDateNegativeVersion5 == false, "lwYear should be positive as pre-version:5 parsing"); lwYear = lwT; ss = ssNil; + if (lwT < 1000 && numOfDigits >= 4) + { + isZeroPaddedYear = true; + } break; } default: @@ -1402,7 +1412,8 @@ namespace Js { // assumptions for getting a YEAR: // - an absolute value greater or equal than 70 (thus not hour!) // - wasn't preceded by negative sign for -version:5 year format - if (lwT >= 70 || isNextFieldDateNegativeVersion5) + // - lwT has at least 4 digits (e.g. 0017 is year 17 AD) + if (lwT >= 70 || isNextFieldDateNegativeVersion5 || numOfDigits >= 4) { // assume it's a year - this is used particularly as version:5 year parsing if (lwNil != lwYear) @@ -1412,6 +1423,11 @@ namespace Js { lwYear = isDateNegativeVersion5 ? -lwT : lwT; isNextFieldDateNegativeVersion5 = false; + if (lwT < 1000 && numOfDigits >= 4) + { + isZeroPaddedYear = true; + } + if (FDateDelimiter(ch)) { // Mark the next token as the month so that it won't be confused with another token. @@ -1499,11 +1515,11 @@ namespace Js { lwYear = -lwYear + 1; } } - else if (lwYear < 50 && isDateNegativeVersion5 == false) + else if (lwYear < 50 && isDateNegativeVersion5 == false && isZeroPaddedYear == false) { lwYear += 2000; } - else if (lwYear < 100 && isDateNegativeVersion5 == false) + else if (lwYear < 100 && isDateNegativeVersion5 == false && isZeroPaddedYear == false) { lwYear += 1900; } diff --git a/test/Date/DateParse2.js b/test/Date/DateParse2.js index 2a4106d812d..d75cdeac3b2 100644 --- a/test/Date/DateParse2.js +++ b/test/Date/DateParse2.js @@ -8,8 +8,7 @@ myPrint("A --"); testDate(new Date(-2012, 1, 2, 1, 2, 3)); testParseDate(new Date(-2012, 1, 2, 1, 2, 3).toString()); -// Disabled due to https://github.com/Microsoft/ChakraCore/issues/4300 -//testParseDate(new Date(-2012, 1, 2, 1, 2, 3).toUTCString()); +testParseDate(new Date(-2012, 1, 2, 1, 2, 3).toUTCString()); testParseDate(new Date(-2012, 1, 2, 1, 2, 3).toISOString()); myPrint("B --"); @@ -27,7 +26,7 @@ testParseDate(new Date(99999, 1, 2, 1, 2, 3).toISOString()); myPrint("D --"); testDate(new Date(-99999, 1, 2, 1, 2, 3)); testParseDate(new Date(-99999, 1, 2, 1, 2, 3).toString()); -//testParseDate(new Date(-99999, 1, 2, 1, 2, 3).toUTCString()); +testParseDate(new Date(-99999, 1, 2, 1, 2, 3).toUTCString()); testParseDate(new Date(-99999, 1, 2, 1, 2, 3).toISOString()); myPrint("E --"); @@ -50,6 +49,10 @@ testParseDate("Tue Feb 02 2012 01:02:03 GMT-0000"); testParseDate("Tue Feb 02 2012 01:02:03 GMT+0430 (prisec@)"); testParseDate("Tue Feb 2 01:02:03 PST 2013 B.C."); testParseDate("Thu Feb 2 01:02:03 PST 2012"); +testParseDate("Thu, 23 Sep -"); +testParseDate("Thu, 23 Sep-"); +testParseDate("Tue Feb 02 -"); +testParseDate("Tue Feb 02-"); function CUT_NAME(str) { return str.replace("(PST)", "(Pacific Standard Time)") diff --git a/test/Date/DateParse2.v5.baseline b/test/Date/DateParse2.v5.baseline index 54854e3274e..5fbd7b69dc9 100644 --- a/test/Date/DateParse2.v5.baseline +++ b/test/Date/DateParse2.v5.baseline @@ -20,6 +20,16 @@ Date string: Tue Feb 02 -2012 01:02:03 GMT-0800 (Pacific Standard Time) -2012/1/2 1:2:3.0 +Date string: Tue, 02 Feb -2012 09:02:03 GMT + raw: -125657017077000 + toString: Tue Feb 02 -2012 01:02:03 GMT-0800 (Pacific Standard Time) + toUTCString: Tue, 02 Feb -2012 09:02:03 GMT + toGMTString: Tue, 02 Feb -2012 09:02:03 GMT + toISOString: -002012-02-02T09:02:03.000Z + 2 -125657017077000 480 + -2012/1/2 + 1:2:3.0 + Date string: -002012-02-02T09:02:03.000Z raw: -125657017077000 toString: Tue Feb 02 -2012 01:02:03 GMT-0800 (Pacific Standard Time) @@ -133,6 +143,16 @@ Date string: Fri Feb 02 -99999 01:02:03 GMT-0800 (Pacific Standard Time) -99999/1/5 1:2:3.0 +Date string: Fri, 02 Feb -99999 09:02:03 GMT + raw: -3217827999477000 + toString: Fri Feb 02 -99999 01:02:03 GMT-0800 (Pacific Standard Time) + toUTCString: Fri, 02 Feb -99999 09:02:03 GMT + toGMTString: Fri, 02 Feb -99999 09:02:03 GMT + toISOString: -099999-02-02T09:02:03.000Z + 2 -3217827999477000 480 + -99999/1/5 + 1:2:3.0 + Date string: -099999-02-02T09:02:03.000Z raw: -3217827999477000 toString: Fri Feb 02 -99999 01:02:03 GMT-0800 (Pacific Standard Time) @@ -165,10 +185,14 @@ Date string: Tue Feb 02 -0012 01:02:03 GMT-0800 (Pacific Standard Time) 1:2:3.0 Date string: Tue, 02 Feb -0012 09:02:03 GMT - raw: NaN - toString: Invalid Date - toUTCString: Invalid Date - toGMTString: Invalid Date + raw: -62543113077000 + toString: Tue Feb 02 -0012 01:02:03 GMT-0800 (Pacific Standard Time) + toUTCString: Tue, 02 Feb -0012 09:02:03 GMT + toGMTString: Tue, 02 Feb -0012 09:02:03 GMT + toISOString: -000012-02-02T09:02:03.000Z + 2 -62543113077000 480 + -12/1/2 + 1:2:3.0 Date string: -000012-02-02T09:02:03.000Z raw: -62543113077000 @@ -292,3 +316,27 @@ Date string: Thu Feb 2 01:02:03 PST 2012 2012/1/4 1:2:3.0 +Date string: Thu, 23 Sep - + raw: NaN + toString: Invalid Date + toUTCString: Invalid Date + toGMTString: Invalid Date + +Date string: Thu, 23 Sep- + raw: NaN + toString: Invalid Date + toUTCString: Invalid Date + toGMTString: Invalid Date + +Date string: Tue Feb 02 - + raw: NaN + toString: Invalid Date + toUTCString: Invalid Date + toGMTString: Invalid Date + +Date string: Tue Feb 02- + raw: NaN + toString: Invalid Date + toUTCString: Invalid Date + toGMTString: Invalid Date + diff --git a/test/Date/parseToStringResults.js b/test/Date/parseToStringResults.js new file mode 100644 index 00000000000..8f2f5c9caa8 --- /dev/null +++ b/test/Date/parseToStringResults.js @@ -0,0 +1,41 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// Date.parse must be able to parse the strings returned by Date.toString() for negative and zero-padded +// years. See https://github.com/Microsoft/ChakraCore/pull/4318 + +// This test is disabled on xplat because the time zone for negative years on xplat is different from +// time zone on Windows. + +/// +if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch + this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); +} + +function testDate(isoDateString) { + let Dateobj = new Date(isoDateString); + let value = Dateobj.valueOf(); + let str = Dateobj.toString(); + + assert.areEqual(value, Date.parse(str), "Date.parse('" + str + "') returns wrong value."); +} + +let tests = [{ + name: "test if Date.parse() can correctly parse outputs of Date.toString()", + body: function () { + testDate("0001-10-13T05:16:33Z"); + testDate("0011-10-13T05:16:33Z"); + testDate("0111-10-13T05:16:33Z"); + testDate("1111-10-13T05:16:33Z"); + + // test BC years + testDate("-000001-11-13T19:40:33Z"); + testDate("-000011-11-13T19:40:33Z"); + testDate("-000111-11-13T19:40:33Z"); + testDate("-001111-11-13T19:40:33Z"); + } +}]; + +testRunner.run(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/Date/parseToUTCStringAndToISOStringResults.js b/test/Date/parseToUTCStringAndToISOStringResults.js new file mode 100644 index 00000000000..bc1c807909e --- /dev/null +++ b/test/Date/parseToUTCStringAndToISOStringResults.js @@ -0,0 +1,41 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// Date.parse must be able to parse the strings returned by Date.toUTCString() and Date.toISOString() +// for negative and zero-padded years. +// See https://github.com/Microsoft/ChakraCore/pull/4318 + +/// +if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch + this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); +} + +function testDate(isoDateString) { + let Dateobj = new Date(isoDateString); + let value = Dateobj.valueOf(); + let UTCstr = Dateobj.toUTCString(); + let ISOstr = Dateobj.toISOString(); + + assert.areEqual(value, Date.parse(UTCstr), "Date.parse('" + UTCstr + "') returns wrong value."); + assert.areEqual(value, Date.parse(ISOstr), "Date.parse('" + ISOstr + "') returns wrong value."); +} + +let tests = [{ + name: "test if Date.parse() can correctly parse outputs of Date.toUTCString() and Date.toISOString()", + body: function () { + testDate("0001-10-13T05:16:33Z"); + testDate("0011-10-13T05:16:33Z"); + testDate("0111-10-13T05:16:33Z"); + testDate("1111-10-13T05:16:33Z"); + + // test BC years + testDate("-000001-11-13T19:40:33Z"); + testDate("-000011-11-13T19:40:33Z"); + testDate("-000111-11-13T19:40:33Z"); + testDate("-001111-11-13T19:40:33Z"); + } +}]; + +testRunner.run(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/Date/rlexe.xml b/test/Date/rlexe.xml index f6538031fa7..1c246c23cd7 100644 --- a/test/Date/rlexe.xml +++ b/test/Date/rlexe.xml @@ -154,4 +154,18 @@ -args summary -endargs + + + parseToStringResults.js + + exclude_xplat + -args summary -endargs + + + + + parseToUTCStringAndToISOStringResults.js + -args summary -endargs + +