RESOLVED FIXED Bug 89071
JavaScript: Invalid date parse for ISO 8601 strings when no timezone given
https://bugs.webkit.org/show_bug.cgi?id=89071
Summary JavaScript: Invalid date parse for ISO 8601 strings when no timezone given
Alexey Androsov
Reported 2012-06-14 01:03:44 PDT
Standard: http://dotat.at/tmp/ISO_8601-2004_E.pdf 4.3.2: The zone designator is empty if use is made of local time in accordance with 4.2.2.2 through 4.2.2.4, it is the UTC designator [Z] if use is made of UTC of day in accordance with 4.2.4 and it is the difference-component if use is made of local time and the difference from UTC in accordance with 4.2.5.2. My timezone is UTC+4, so date without timezone must be parsed as localtime. Example: 2012-06-14T05:00:00 -> 2012-06-14T05:00:00+04:00 -> 2012-06-14T01:00:00Z valid result (Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:13.0) Gecko/20100101 Firefox/13.0): new Date('2012-06-14T05:00:00').toISOString() -> "2012-06-14T01:00:00.000Z" invalid result (Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5): new Date('2012-06-14T05:00:00').toISOString() -> "2012-06-14T05:00:00.000Z"
Attachments
Patch (13.35 KB, patch)
2020-01-03 21:19 PST, Ross Kirsling
no flags
Patch (16.40 KB, patch)
2020-01-04 00:24 PST, Ross Kirsling
no flags
Patch for landing (16.05 KB, patch)
2020-01-05 00:11 PST, Ross Kirsling
no flags
Gavin Barraclough
Comment 1 2012-09-25 13:52:45 PDT
OOI ecmascript date parsing is not ISO 8601 compliant, but we may be able to make this case work.
othree
Comment 2 2019-12-19 07:15:57 PST
In latest ECMAScript spec 20.4.3.2 <https://tc39.es/ecma262/#sec-date.parse> There is a line talked about date time string without timezone should be local time: "When the UTC offset representation is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time."
Ross Kirsling
Comment 3 2019-12-19 10:49:53 PST
Created a test262 issue as well, since there's currently no conformance test coverage for this case: https://github.com/tc39/test262/issues/2450
Ross Kirsling
Comment 4 2020-01-03 15:17:06 PST
*** Bug 205652 has been marked as a duplicate of this bug. ***
Ross Kirsling
Comment 5 2020-01-03 15:19:23 PST
*** Bug 196894 has been marked as a duplicate of this bug. ***
Ross Kirsling
Comment 6 2020-01-03 21:19:28 PST
Ross Kirsling
Comment 7 2020-01-04 00:24:37 PST
Ross Kirsling
Comment 8 2020-01-04 12:59:43 PST
Comment hidden (obsolete)
Darin Adler
Comment 9 2020-01-04 20:33:26 PST
Comment on attachment 386760 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=386760&action=review Not a huge fan of "out arguments" but we should respect the style of this old-ish code, I suppose. > Source/JavaScriptCore/runtime/JSDateMath.cpp:218 > + int offset = isLocalTime ? localTimeOffset(vm, ms, WTF::LocalTime).offset : 0; > + return ms - offset; I don't think it’s great to use the trinary operator here. How about this? if (isLocalTime) ms -= localTimeOffset(vm, ms, WTF::LocalTime).offset; return ms; Also, maybe instead of "ms" we could name the local variable "value", or "date", or "time". I think a word is better than an SI unit for a variable name. > Source/WTF/wtf/DateMath.cpp:575 > + isLocalTime = false; Why not do this at the very top of the function instead of down here? > Source/WTF/wtf/DateMath.cpp:653 > + isLocalTime = false; Why not do this at the start of the function rather than here in this else?
Ross Kirsling
Comment 10 2020-01-04 23:59:30 PST
(In reply to Darin Adler from comment #9) > Comment on attachment 386760 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=386760&action=review > > Not a huge fan of "out arguments" but we should respect the style of this > old-ish code, I suppose. Agreed. > > Source/WTF/wtf/DateMath.cpp:575 > > + isLocalTime = false; > > Why not do this at the very top of the function instead of down here? > > > Source/WTF/wtf/DateMath.cpp:653 > > + isLocalTime = false; > > Why not do this at the start of the function rather than here in this else? I thought the logic was easier to follow by keeping it more local, but I can move these up.
Ross Kirsling
Comment 11 2020-01-05 00:11:17 PST
Created attachment 386783 [details] Patch for landing
WebKit Commit Bot
Comment 12 2020-01-05 00:55:39 PST
Comment on attachment 386783 [details] Patch for landing Clearing flags on attachment: 386783 Committed r254038: <https://trac.webkit.org/changeset/254038>
WebKit Commit Bot
Comment 13 2020-01-05 00:55:41 PST
All reviewed patches have been landed. Closing bug.
Radar WebKit Bug Importer
Comment 14 2020-01-05 00:56:20 PST
Darin Adler
Comment 15 2020-01-20 09:52:07 PST
Comment on attachment 386783 [details] Patch for landing View in context: https://bugs.webkit.org/attachment.cgi?id=386783&action=review > Source/JavaScriptCore/runtime/JSDateMath.cpp:220 > + bool isLocalTime; > + double value = WTF::parseES5DateFromNullTerminatedCharacters(dateString, isLocalTime); > + if (std::isnan(value)) > + return std::numeric_limits<double>::quiet_NaN(); > + > + if (isLocalTime) > + value -= localTimeOffset(vm, value, WTF::LocalTime).offset; > + > + return value; Had a small thought for refinement: - When parseES5DateFromNullTerminatedCharacters returns a NaN, there’s no need to "sanitize" it. - Calling localTimeOffset has no side effects. - Subtracting a number from a NaN has no effect on the NaN. Assuming I am correct, and all three of these are in fact true, we can remove the special case if statement for NaN with these effects: - Slightly smaller code. - Slightly faster for the non-NaN case. - Slightly slower for the NaN case. So, I think we should remove it.
Ross Kirsling
Comment 16 2020-01-21 10:15:38 PST
(In reply to Darin Adler from comment #15) > Comment on attachment 386783 [details] > Patch for landing > > View in context: > https://bugs.webkit.org/attachment.cgi?id=386783&action=review > > > Source/JavaScriptCore/runtime/JSDateMath.cpp:220 > > + bool isLocalTime; > > + double value = WTF::parseES5DateFromNullTerminatedCharacters(dateString, isLocalTime); > > + if (std::isnan(value)) > > + return std::numeric_limits<double>::quiet_NaN(); > > + > > + if (isLocalTime) > > + value -= localTimeOffset(vm, value, WTF::LocalTime).offset; > > + > > + return value; > > Had a small thought for refinement: > > - When parseES5DateFromNullTerminatedCharacters returns a NaN, there’s no > need to "sanitize" it. > - Calling localTimeOffset has no side effects. > - Subtracting a number from a NaN has no effect on the NaN. > > Assuming I am correct, and all three of these are in fact true, we can > remove the special case if statement for NaN with these effects: > > - Slightly smaller code. > - Slightly faster for the non-NaN case. > - Slightly slower for the NaN case. > > So, I think we should remove it. Yeah, the NaN paths in both of those static functions are a bit sad since we call each from one place, and in particular, the latter only gets called if the former returned NaN. I didn't realize that "[s]ubtracting a number from a NaN has no effect on the NaN" in C++, that's good to know. Calling `localTimeOffset` does have a caching side effect; not sure whether that changes your mind at all. What I had considered myself here was merging the two static functions into one (and just returning the unsanitized NaN since we already know it's quiet_NaN), to avoid the icky redundancy of rechecking whether we took the NaN path on the first *after* returning from it. I could still do that.
Keith Miller
Comment 17 2020-01-21 10:21:40 PST
Comment on attachment 386783 [details] Patch for landing View in context: https://bugs.webkit.org/attachment.cgi?id=386783&action=review >> Source/JavaScriptCore/runtime/JSDateMath.cpp:220 >> + return value; > > Had a small thought for refinement: > > - When parseES5DateFromNullTerminatedCharacters returns a NaN, there’s no need to "sanitize" it. > - Calling localTimeOffset has no side effects. > - Subtracting a number from a NaN has no effect on the NaN. > > Assuming I am correct, and all three of these are in fact true, we can remove the special case if statement for NaN with these effects: > > - Slightly smaller code. > - Slightly faster for the non-NaN case. > - Slightly slower for the NaN case. > > So, I think we should remove it. We do care about the type of NaN we pass to jsNumber because it doesn't check that the NaN is pure when boxing (other than a debug assert). Although, in theory the C++ spec doesn't guarantee quient_NaN is also a pure NaN, so it might be reasonable to change the `std::numeric_limits<double>::quiet_NaN()` to `PNaN`.
Keith Miller
Comment 18 2020-01-21 10:22:59 PST
Comment on attachment 386783 [details] Patch for landing View in context: https://bugs.webkit.org/attachment.cgi?id=386783&action=review >>>> Source/JavaScriptCore/runtime/JSDateMath.cpp:220 >>>> + return value; >>> >>> Had a small thought for refinement: >>> >>> - When parseES5DateFromNullTerminatedCharacters returns a NaN, there’s no need to "sanitize" it. >>> - Calling localTimeOffset has no side effects. >>> - Subtracting a number from a NaN has no effect on the NaN. >>> >>> Assuming I am correct, and all three of these are in fact true, we can remove the special case if statement for NaN with these effects: >>> >>> - Slightly smaller code. >>> - Slightly faster for the non-NaN case. >>> - Slightly slower for the NaN case. >>> >>> So, I think we should remove it. >> >> Yeah, the NaN paths in both of those static functions are a bit sad since we call each from one place, and in particular, the latter only gets called if the former returned NaN. >> >> I didn't realize that "[s]ubtracting a number from a NaN has no effect on the NaN" in C++, that's good to know. Calling `localTimeOffset` does have a caching side effect; not sure whether that changes your mind at all. >> >> What I had considered myself here was merging the two static functions into one (and just returning the unsanitized NaN since we already know it's quiet_NaN), to avoid the icky redundancy of rechecking whether we took the NaN path on the first *after* returning from it. I could still do that. > > We do care about the type of NaN we pass to jsNumber because it doesn't check that the NaN is pure when boxing (other than a debug assert). Although, in theory the C++ spec doesn't guarantee quient_NaN is also a pure NaN, so it might be reasonable to change the `std::numeric_limits<double>::quiet_NaN()` to `PNaN`. I guess I should also mention that the result of this function is indirectly passed to jsNumber, in case that wasn't clear.
Darin Adler
Comment 19 2020-01-21 10:30:35 PST
That sounds wrong to me. I think we are guaranteed this is a pure NaN.
Keith Miller
Comment 20 2020-01-21 12:03:48 PST
(In reply to Darin Adler from comment #19) > That sounds wrong to me. I think we are guaranteed this is a pure NaN. I believe on MIPS, for instance, QNaN has the sign bit set so it will be impure. At least, that's my understanding from: https://github.com/WebAssembly/design/issues/976.
Keith Miller
Comment 21 2020-01-21 12:05:20 PST
(In reply to Keith Miller from comment #20) > (In reply to Darin Adler from comment #19) > > That sounds wrong to me. I think we are guaranteed this is a pure NaN. > > I believe on MIPS, for instance, QNaN has the sign bit set so it will be > impure. At least, that's my understanding from: > https://github.com/WebAssembly/design/issues/976. We could add a static_assert somewhere that quiet_NaN is pure though.
Ross Kirsling
Comment 22 2020-01-21 16:22:25 PST
Created bug 206560 to address the "excessive NaN checking" concern. I'm hoping the refactor presented there makes the direct issue moot.
Yusuke Suzuki
Comment 23 2020-01-21 22:41:47 PST
*** Bug 160287 has been marked as a duplicate of this bug. ***
Note You need to log in before you can comment on or make changes to this bug.