I try to get UTC Date. Why
(new Date("2015-04-22")).getUTCDate() = 22
And
(new Date("Apr 22, 2015")).getUTCDate() = 21
?
To quote MDN on the workings of Date.parse (called by new Date("string")):
Differences in assumed time zone
Given a date string of "March 7, 2014", parse() assumes a local time zone, but given an ISO format such as "2014-03-07" it will assume
a time zone of UTC. Therefore Date objects produced using those
strings will represent different moments in time unless the system is
set with a local time zone of UTC. This means that two date strings
that appear equivalent may result in two different values depending on
the format of the string that is being converted (this behavior is
changed in ECMAScript ed 6 so that both will be treated as local).
Watch out for the warning I've highlighted on the last line
Related
I live in the UK. If I create a date object for 1st June, I would have expected .getUTCDate() to return the "correct" day of the month as 1, but instead it returns 31 and .getDate(), which I thought was meant to return the locale date returns 1.
new Date(2020,5,1).getUTCDate() // 31
new Date(2020,5,1).getDate(); // 1
Why is this? Is it because new Date(2020,5,1) is already converted to local time or something? I thought dates were stored universally, and it is only when the date is printed out that the locale rules are applied? I've read all the MDN docs and still don't understand, I would really appreciate if someone can walk through the steps of what happens for the above to return 31 and 1.
A brief explanation of how ECMAScript Date constructor works.
Single string values are parsed and converted to Dates in weird and wonderful ways that are mostly implementation dependent, see below. So all of the following ignores parsing and assumes the Date constructor is called with at least two arguments, year and month. In this case, string values are converted to number as if by Number(value).
All the non–UTC methods work in local time, i.e. based on the host system date, time and timezone offset settings. When creating a Date object using the constructor and values for year, month, day, etc. then any missing values are treated as 0 except for the day, which defaults to 1.
So
new Date(2020, 5, 1)
creates a date based on the host (local) settings for date, time and timezone offset for that date and time, respecting historical changes like daylight saving and other adjustments (common before 1900) as if by:
new Date(2020, 5, 1, 0, 0, 0, 0); // Local 1 Jun 2020 00:00:00.000
So getUTCDate will return 31 (i.e. 31 May) for systems with a timezone offset that is less than zero and 1 (i.e. 1 Jun) for any with an offset of zero or greater. The largest positive offset in common use is +14 and the smallest is -10.
If you want to create a Date based on UTC values, then use the Date.UTC method, whcih returns a number, the time value, so you have to pass it to the Date constructor to create a Date object:
new Date(Date.UTC(2020, 5, 1)).getUTCDate(); // 1 regardless of host settings
Parsing strings: new Date(string) and Date.parse(string)
This where the Date object crashes and burns. While the constructor mostly uses local, it can also use UTC even where UTC is not specified. Parsing of strings is almost entirely implementation dependent except for the 3 formats supported by ECMA-262. Even the supported formats are not parsed consistently across implementations so the general advice is do not use the built–in parser. See Why does Date.parse give incorrect results?
This question already has answers here:
Why does Date.parse give incorrect results?
(11 answers)
Closed 7 years ago.
I was executing below statement under nodejs repl and i was getting two different result for same date
var dateStr1 = "2015/03/31";
var dateStr2 = "2015-03-31";
var date1 = new Date(dateStr1);//gives Tue Mar 31 2015 00:00:00 GMT+0530 (IST)
var date2 = new Date(dateStr2);//gives Tue Mar 31 2015 05:30:00 GMT+0530 (IST)
In the 1st one hour,min,seconds are all zeros while in the 2nd one by default hour,min is getting set to as a timezone hour,min which is 5:30
It boils down to how Date.parse() handles the ISO-8601 date format.
The date time string may be in ISO 8601 format. For example, "2011-10-10" (just date) or "2011-10-10T14:48:00" (date and time) can be passed and parsed. The UTC time zone is used to interpret arguments in ISO 8601 format that do not contain time zone information (note that ECMAScript ed 6 draft specifies that date time strings without a time zone are to be treated as local, not UTC)
Your first date format 2015/03/31 is assumed to be March 31st, 2015 at 12am in your current time zone.
Your second date format 2015-03-31 is seen as ISO-8601 and is assumed to be March 31st, 2015 at 12am UTC time zone.
The "Differences in assumed time zone" heading from the linked documentation goes into a bit more detail:
Given a date string of "March 7, 2014", parse() assumes a local time zone, but given an ISO format such as "2014-03-07" it will assume a time zone of UTC. Therefore Date objects produced using those strings will represent different moments in time unless the system is set with a local time zone of UTC. This means that two date strings that appear equivalent may result in two different values depending on the format of the string that is being converted (this behavior is changed in ECMAScript ed 6 so that both will be treated as local).
The first string, "2015/03/31", is an unsupported format according to the ECMAScript standard. Whenever an unsupported value is passed to the constructor its behaviour is implementation dependent, i.e. the standard doesn't say what the implementation must do. Some browsers, like Firefox, try to guess what the format is and apparently it creates a date object with that date at midnight local time. Other browsers may return NaN or interpret the parts differently.
The second string, "2015-03-31", is a properly formatted ISO 8601 date. For these strings there are well defined rules and all browsers will interpret it as that date, midnight, UTC.
I am providing dates two to my script in this format: 2016-05-25 12:30:02. However, JavaScript is setting the each date to a different GMT offset. I formatted the dates to match the suggested answer here and added the function provided, but I got the same results as shown below so I reverted back to my original script shown below.
var response = JSON.parse(jsonResponse);
var lastExportedOrderData = response.lastExportedOrder;
/* currentDateTime = '2016-05-25 12:30:02'; */
var currentDateTime = new Date(response.scriptExecutionTime);
/* lastOrderExportedAt = '2016-01-12 16:53:56'; */
var lastOrderExportedAt = new Date(lastExportedOrderData.exported_at);
currentDateTime Results: 2016-05-25 12:30:02 -> Wed May 25 2016 12:30:02 GMT-0400 (EDT)
lastOrderExportedAt Results: 2016-01-12 16:53:56 > Tue Jan 12 2016 16:53:56 GMT-0500 (EST)
I don't really care about the timezone so long as the dates are in the same timezone.
Update
My dates are now being input using the ISO-8601 Standard format but my the problem remains. 2016-05-25T14:04:00-05:00 results as GMT-0400 where 2016-01-12T16:53:56-05:00 results as GMT -0500
They're in the same timezone (U.S. Eastern). The difference is that the one in May is during Daylight Savings Time and thus is Eastern Daylight Time (EDT), and the one in January isn't, and thus is in Eastern Standard Time.
It's important to note that their underlying time values (e.g., as from dt.getTime()) are unaffected by timezone, and as both dates have been parsed (in your example) as local time, they can reliably be compared. You'd only run into an issue comparing them if one were parsed as local time and the other as UTC.
Side note: You're relying on new Date to parse those strings, which means you're relying on unspecified behavior that may vary from JavaScript engine to JavaScript engine. The only string format engines are required to support is a simplified version of ISO-8601, which those strings are not in. In particular, it's entirely possible that one browser's JavaScript engine will decide to parse them as though they were in UTC and another engine's browser will decide (as yours seems to have done) to parse them as local time.
You haven't said what timezone the strings are meant to be interpreted in, but if (for instance) they were meant to be UTC, you can fairly easily change them to be in ISO-8601 format by replacing the space between the date and time with a T, and adding a Z to the end:
str = str.replace(" ", "T") + "Z";
The resulting string, for instance "2016-05-25T12:30:02Z", is valid for new Date on any modern JavaScript engine (so, not IE8).
Alternately, if you know what timezone offset they should be interpreted in (say, GMT-05:00), you can replace the space with T and add a timezone offset to the end:
str = str.replace(" ", "T") + "-05:00";
That string (for instance, "2016-05-25T12:30:02-05:00") is also valid for new Date.
Alternately, get the parts of them:
var parts = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(str);
...and build the date/time yourself
// As local time
var dt = new Date(
+parts[1], // Year
+parts[2] - 1, // Month (0 = January)
+parts[3], // Day
+parts[4], // Hour
+parts[5], // Minute
+parts[6] // Second
);
or
// As UTC
var dt = new Date(Date.UTC(
+parts[1], // Year
+parts[2] - 1, // Month (0 = January)
+parts[3], // Day
+parts[4], // Hour
+parts[5], // Minute
+parts[6] // Second
));
The unary + on the above coerces from string to number. (It isn't strictly necessary, though, both new Date and Date.UTC do it for you.)
Just to add to TJ's answer, in implementations consistent with ES5 and later, the string 2016-05-25T14:04:00 will be parsed as a "local" time as it doesn't have a time zone offset. (In an ES2015-compliant browser. Sadly in the previous specification, they got that wrong and made it default to UTC, and currently Chrome implements the ES5 spec [UTC] but Firefox implements the ES2015 spec [local time].) But don't rely on that, parse the string per TJ's answer.
When creating a local time value, the host time zone settings are taking in to consideration. It seems that the host system is set for EST/EDT.
EDT starts at 02:00 on the second Sunday in March, so on 2016-01-12 US Eastern Standard Time (EST) applies which has an offset of -0500. For 2016-05-25, US Eastern Daylight Time (EDT) is used which has an offset of -0400.
Note that the actual time value of the Date created for 2016-05-25T12:30:02-0400 is for 2016-05-25T16:30:02Z (i.e. the equivalent UTC time). When displayed, the date's toString method is called which applies the host system offset for that time and date to adjust the displayed value to "local" time.
Date's use UTC internally so that they can be displayed in any time zone and still represent the same instant in time.
I'm trying to pass both date strings to new Date(t).
I expect both strings represent the same time, after all, if I omit the time, shouldn't it be midnight of that day?
But while,
new Date("2016-02-16 00:00")
returns 2016-02-16, midnight, local time as expected,
new Date("2016-02-16")
returns 2016-02-16, midnight UTC, which is wrong, or at least not what I expected given what the other string parses as.
I would understand it if they would both have the same behavior, whether it is to return the time as local time, or as UTC, but it seems very inconsistent why they return different things like this.
As a workaround, whenever I encounter a date that has no corresponding timestamp I can append " 00:00" to get consistent behavior, but it seems like this is rather fragile.
I am getting this value from an INPUT element, of type 'datetime-local' so it seems especially inconsistent that I have to work around a value returned by a page element.
Am I doing something wrong, or should I be doing something differently?
It's what the ES5.1 specification says to do:
The value of an absent time zone offset is “Z”.
It also says:
The function first attempts to parse the format of the String according to the rules called out in Date Time String Format (15.9.1.15). If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats.
Since the format requires a T separator between date and time, the valid times go to UTC:
> new Date("2016-02-16T00:00:00")
Tue Feb 16 2016 01:00:00 GMT+0100 (CET)
> new Date("2016-02-16")
Tue Feb 16 2016 01:00:00 GMT+0100 (CET)
...while in node.js, an invalid time (without the T separator) seems to go to the implementation specific localtime:
> new Date("2016-02-16 00:00:00")
Tue Feb 16 2016 00:00:00 GMT+0100 (CET)
Note that ES6 changed this, in the same part of the documentation it changes to:
If the time zone offset is absent, the date-time is interpreted as a local time.
The joy of breaking changes.
Edit
According to TC39, the specification is meant to be interpreted as date and time strings without a time zone (e.g. "2016-02-16T00:00:00") are treated as local (per ISO 8601), but date only strings (e.g. "2016-02-16") as UTC (which is inconsistent with ISO 8601).
According to the specifications:
The function first attempts to parse the format of the String
according to the rules called out in Date Time String Format
(15.9.1.15). If the String does not conform to that format the
function may fall back to any implementation-specific heuristics or
implementation-specific date formats.
And Date Time String Formats accept 2016-02-16 as a valid date
This format includes date-only forms:
YYYY
YYYY-MM
YYYY-MM-DD
[...] If the HH, mm, or ss fields are absent “00” is used as the value
and the value of an absent sss field is “000”. The value of an absent
time zone offset is “Z”.
Thus 2016-02-16 translates to 2016-02-16T00:00:00.000Z.
The other date 2016-02-16 00:00 does not conform to the format and therefore its parsing is implementation specific. Apparently, such dates are treated as having local time zone and your example date will return different values depending on time zone:
/* tz = +05:00 */ new Date("2016-02-16 00:00").toISOString() // 2016-02-15T19:00:00.000Z
/* tz = -08:00 */ new Date("2016-02-16 00:00").toISOString() // 2016-02-16T08:00:00.000Z
Summary:
For conforming date time formats the behavior is well defined — in the absence of time zone offset the date string is treated as UTC (ES5) or local (ES6).
For non-conforming date time formats the behavior is implementation specific — in the absence of time zone offset the usual behavior is to treat the date as local.
As a matter of fact, the implementation could choose to return NaN instead of trying to parse non-conforming dates. Just test your code in Internet Explorer 11 ;)
You are perhaps running into a differences between ES5, ES6 implementations and your expected result. Per Date.parse at MDN, "especially across different ECMAScript implementations where strings like "2015-10-12 12:00:00" may be parsed to as NaN, UTC or local timezone" is significant.
Additional testing in Firefox 44 and IE 11 revealed they both return a date object for new Date("2016-02-16 00:00"), which object returns NaN when atttempting to get a date component value, and whose toString value is
"Invalid Date" (not "NaN"). Hence appending " 00:00 to get consistent behavior" can easily break in different browsers.
As noted in other answers new Date("2016-02-16") uses a timezone offset of zero by default, producing midnight UTC instead of local.
Per DateParser::Parse() of V8 source codes for Chrome.
ES5 ISO 8601 dates:
[('-'|'+')yy]yyyy[-MM[-DD]][THH:mm[:ss[.sss]][Z|(+|-)hh:mm]]
An unsigned number followed by ':' is a time value, and is added to the TimeComposer.
timezone defaults to Z if missing
> new Date("2016-02-16 00:00")
Tue Feb 16 2016 00:00:00 GMT+0800 (China Standard Time)
A string that matches both formats (e.g. 1970-01-01) will be parsed as an ES5 date-time string - which means it will default to UTC time-zone. That's unavoidable if following the ES5 specification.
> new Date("2016-02-16")
Tue Feb 16 2016 08:00:00 GMT+0800 (China Standard Time)
returns 2016-02-16, midnight UTC, which is wrong, or at least not what I expected given what the other string parses as.
It adds the timezone offset to the 00:00
new Date("2016-02-16") outputs Tue Feb 16 2016 05:30:00 GMT+0530 (India Standard Time)
My timezone being IST with an offset value (in minutes) +330, so it added 330 minutes to 00:00.
As per ecma-262, section 20.3.3.2 Date.parse ( string )
If ToString results in an abrupt completion the Completion Record is
immediately returned. Otherwise, parse interprets the resulting String
as a date and time; it returns a Number, the UTC time value
corresponding to the date and time. The String may be interpreted as a
local time, a UTC time, or a time in some other time zone, depending
on the contents of the String.
When you explicitly set the time-units new Date("2016-02-16 00:00") it wll use set that as hours and minutes,
Otherwise as stated here in 20.3.1.16
If the time zone offset is absent, the date-time is interpreted as a
local time.
The following outputs a time that is 4 hours less than what was input:
X = new Date('2015-07-09T14:18:12.430')
$('body').append(X)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
When you do no specify the timezone:
ECMAScript-5 compliant browsers will assume UTC timezone:
The value of an absent time zone offset is "Z".
ECMAScript-6 compliant browsers will assume local timezone:
If the time zone offset is absent, the date-time is interpreted as a
local time.
Use the long Date constructor which assumes local timezone:
var X = new Date(2015, 7 - 1, 9, 14, 18, 12, 430);
alert(X);
When you omit the timezone information on the end of an ISO 8601 formatted date-time the majority of computer implementations default to +0000, this means Date interprets 2015-07-09T14:18:12.430 the same as 2015-07-09T14:18:12.430+0000 or 2015-07-09T14:18:12.430Z
It looks like you were expecting it to be interpreted as your local time rather than in UTC, you have 3 options
Append your local timezone offset to the end
Write it differently so it will parse as you expect
Create a second Date using the details from the UTC fields of the first Date
function myParser(iso_style) {
var d = new Date(iso_style);
return new Date(
d.getUTCFullYear(),
d.getUTCMonth(),
d.getUTCDate(),
d.getUTCHours(),
d.getUTCMinutes(),
d.getUTCSeconds(),
d.getUTCMilliseconds()
);
}
You can't simply adjust by your local offset as you would experience unexpected behaviour if the time crosses a date that would change that offset e.g. a daylight savings boundry
As Salman A's answer points out the current ES 6 Draft defines that an omitted timezone should be interpreted as meaning the client's local timezone. This will make the behaviour inconsistent as different JavaScript implementations change over so I strongly recommend you always use a timezone when writing ISO 8601.
A quick and dirty workaround:
X = new Date('2015-07-09T14:18:12.430');
$('body').append( X.toString().split('GMT')[0] );
It works by turning the date object into a string, then splitting it into an array around the pattern GMT. So you would have 2 elements in the array: Thu Jul 09 2015 10:18:12 and 0400 (EDT). Since you want to drop everything after the GMT, you just use the element in the 0 index of the array
I misunderstood the question. If you use X.toUTCString(), it may fix your issue.