I tried with two methods to generate Date first by passing whole date string and second with year, month, day combination. But I am getting different outputs while the same date is being provided. The Day is not right. It should be 30 June in the first too.
const oldDate = new Date('2020-06-30');
const newDate = new Date('2020', '05', '30');
console.log(oldDate.toString(), newDate.toString());
When you instantiate a Date by passing a string, it's supposed to be a full ISO 8601 string which specifies the time zone. As you dont specify it, it takes GMT+0, and you seem to be located at GMT-7. You should write this instead:
console.log(new Date('2020-06-30T00:00:00-07:00').toString());
The Date constructor that accepts multiple arguments expects them to be numbers, and accepts the month number as a 0-based value (0 = January). The values are expected to be in local time.
The Date constructor accepting a single string argument parses the string according to the specified rules (which apply to your example) and, possibly, unspecified fallback rules the JavaScript engine implementer chose to add (in your case, though, the fallback isn't necessary). When there's no timezone indicator on the string, Date-only forms such as yours are parsed in UTC (date/time forms are parsed in local time).
(The Date constructor accepting a single number expects that number to be milliseconds-since-The-Epoch [Jan 1st, 1970 at midnight, UTC].)
Below format is considered as GMT time and it tries to convert to your local timezone. That's why you notice 7 hours subtracted.
new Date('2020-06-30')
whereas,
Below format is considered as local timezone and no further conversion happen.
new Date('2020', '05', '30');
According to MDN docs:
dateString
A string value representing a date, specified in a format recognized by the Date.parse() method. (These formats are IETF-compliant RFC 2822 timestamps, and also strings in a version of ISO8601.)
Note: Parsing of date strings with the Date constructor (and Date.parse(), which works the same way) is strongly discouraged due to browser differences and inconsistencies.
Support for RFC 2822 format strings is by convention only.
Support for ISO 8601 formats differs in that date-only strings (e.g. "1970-01-01") are treated as UTC, not local.
Therefore, when you create date via new Date("2020-06-30") it creates date object in 0 timezone and adjusts time to show it equal to your time zone.
Related
I wish to create a new Date in JS, but have it be cast as UTC time. For example, suppose castAsUTC() produces the following desired effect:
var x = new Date('2019-01-01T00:00:00') // In local time (PST)
castAsUTC(x).toISOString(); // => '2019-01-01T00:00:00Z'
// x.toISOString() gives us '2019-01-01T08:00:00Z', which is undesired
Currently, my function looks like this:
function castAsUTC(date) {
return new Date(x.toLocaleString() + '+00:00');
}
Is there a cleaner/nicer way of producing the same effect? Thanks in advance!
EDIT: To be more specific, I'm interested in transforming the date's timezone, without changing its actual value with as little arithmetic as possible. So calling .toISOString() will produce the same date as it is in local time.
I am currently using the moment-timezone library, but I can't seem to get the desired effect using that, either. I would definitely accept an answer that uses Moment.js
You can switch a Moment instance to UTC using the utc function. Then just use format to get whatever the specific output you want from it.
If indeed the string you have is like the one shown, then the easiest thing to do would be to append a Z to indicate UTC.
var input = '2019-01-01T00:00:00';
var date = new Date(input + 'Z');
var output = date.toISOString();
Or, if you would like to use Moment.js, then do this:
var input = '2019-01-01T00:00:00';
var m = moment.utc(input);
var output = m.format();
You do not need moment-timezone for this.
tl;dr;
You formatted the date wrong. Add the letter "Z" to the end of your date string and it will be treated as UTC.
var x = new Date('2019-01-01T00:00:00Z') // Jan 1, 2019 12 AM UTC
These formatting issues are easier to manage with a library like momentjs (utc and format functions) as described in other answers. If you want to use vanilla javascript, you'll need to subtract out the timezone offset before calling toISOString (see warnings in the longer answer below).
Details
Date in javascript deals with timezones in a somewhat counter intuitive way. Internally, the date is stored as the number of milliseconds since the Unix epoch (Jan 1, 1970). That's the number you get when you call getTime() and it's the number that's used for math and comparisons.
However - when you use the standard string formatting functions (toString, toTimeString, toDateString, etc) javascript automatically applies the timezone offset for the local computers timezone before formatting. In a browser, that means it will apply the offset for the end users computer, not the server. The toISOString and toUTCString functions will not apply the offset - they print the actual UTC value stored in the Date. This will probably still look "wrong" to you because it won't match the value you see in the console or when calling toString.
Here's where things really get interesting. You can create Date's in javascript by specifying the number of milliseconds since the Unix epoch using new Date(milliseconds) or by using a parser with either new Date(dateString). With the milliseconds method, there's no timezone to worry about - it's defined as UTC. The question is, with the parse method, how does javascript determine which timezone you intended? Before ES5 (released 2009) the answer was different depending on the browser! Post ES5, the answer depends on how you format the string! If you use a simplified version of ISO 8601 (with only the date, no time), javascript considers the date to be UTC. Otherwise, if you specify the time in ISO 8601 format, or you use a "human readable" format, it considers the date to be local timezone. Check out MDN for more.
Some examples. I've indicated for each if javascript treats it as a UTC or a local date. In UTC, the value would be Jan 1, 1970 at midnight. In local it depends on the timezone. For OP in pacfic time (UTC-8), the UTC value would be Jan 1, 1970 at 8 AM.
new Date(0) // UTC (milliseconds is always UTC)
new Date("1/1/1970"); // Local - (human readable string)
new Date("1970-1-1"); // Local (invalid ISO 8601 - missing leading zeros on the month and day)
new Date("1970-01-01"); // UTC (valid simplified ISO 8601)
new Date("1970-01-01T00:00"); // Local (valid ISO 8601 with time and no timezone)
new Date("1970-01-01T00:00Z"); // UTC (valid ISO 8601 with UTC specified)
You cannot change this behavior - but you can be pedantic about the formats you use to parse dates. In your case, the problem was you provided an ISO-8601 string with the time component but no timezone. Adding the letter "Z" to the end of your string, or removing the time would both work for you.
Or, always use a library like momentjs to avoid these complexities.
Vanilla JS Workaround
As discussed, the real issue here is knowing whether a date will be treated as local or UTC. You can't "cast" from local to UTC because all Date's are UTC already - it's just formatting. However, if you're sure a date was parsed as local and it should really be UTC, you can work around it by manually adjusting the timezone offset. This is referred to as "epoch shifting" (thanks #MattJohnson for the term!) and it's dangerous. You actually create a brand new Date that refers to a different point in time! If you use it in other parts of your code, you can end up with incorrect values!
Here's a sample epoch shift method (renamed from castAsUtc for clarity). First get the timezone offset from the object, then subtract it and create a new date with the new value. If you combine this with toISOString you'll get a date formatted as you wanted.
function epochShiftToUtc(date) {
var timezoneOffsetMinutes = date.getTimezoneOffset();
var timezoneOffsetMill = timezoneOffsetMinutes * 1000 * 60;
var buffer = new Date(date.getTime() - timezoneOffsetMill);
return buffer;
}
epochShiftToUtc(date).toUTCString();
How do you correctly intitialize a timezone-independent date (or I guess a date that is fixed to a single timezone across the html side and the JS side) from a html datepicker?
I have the following simple code, which is producing incorrect dates:
function printDate(){
let d = new Date(document.getElementById("date").value)
alert(d)
}
document.getElementById("printDate").addEventListener("click", e => printDate())
<html>
<body>
Print Date: <br><input type="date" id="date"> <button id="printDate">Add</button>
</body>
</html>
But at least on my computer, currently sitting in U.S. mountain time, it produces incorrect dates. I give it today's date (March 9, 2019), and it alerts yesterday's date in the following format: Fri Mar 08 2019 17:00:00 GMT-0700 (MST). How do I make it not do that?
I really just want it to assume that all input and all output are in GMT.
In a <input type="date" /> element, the selected date is displayed in the locale format, but the value property is always returned in yyyy-mm-dd format, as described in the MDN docs.
In other words, when you choose March 9, 2019, you may see 03/09/2019 from the US or 09/03/2019 in other parts of the world, but value is 2019-03-09 regardless of any time zone or localization settings. This is a good thing, as it allows you to work with the selected date in a standard ISO 8601 format, without trying to apply a time.
However, when you parse a date string in that format with the Date object's constructor (or with Date.parse), you run up against a known issue: The date is not treated as local time, but as UTC. This is the opposite of ISO 8601.
This is described in the MDN docs:
Note: parsing of date strings with the Date constructor (and Date.parse, they are equivalent) is strongly discouraged due to browser differences and inconsistencies. Support for RFC 2822 format strings is by convention only. Support for ISO 8601 formats differs in that date-only strings (e.g. "1970-01-01") are treated as UTC, not local.
It's also in the ECMAScript specification (emphasis mine):
... When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time.
There was a debate about this in 2015, but ultimately it was decided that maintaining compatibility with existing behaviors was more important than being ISO 8601 compliant.
Going back to your question, the best thing to do would be to not parse it into a Date object if you don't need one. In other words:
function printDate(){
const d = document.getElementById("date").value;
alert(d);
}
If you really need a Date object, then the easiest option is to parse the value yourself:
function printDate(){
const parts = document.getElementById("date").value.split('-');
const d = new Date(+parts[0], parts[1]-1, +parts[2], 12);
alert(d);
}
Note the ,12 at the end sets the time to noon instead of midnight. This is optional, but it avoids situations of getting the wrong day when midnight doesn't exist in the local time zone where DST transitions at midnight (Brazil, Cuba, etc.).
Then there's your last comment:
I really just want it to assume that all input and all output are in GMT.
That's a bit different than what you showed. If really that's what you want, then you can construct the Date object as you previously did, and use .toISOString(), .toGMTString(), or .toLocaleString(undefined, {timeZone: 'UTC'})
function printDate(){
const d = new Date(document.getElementById("date").value); // will treat input as UTC
// will output as UTC in ISO 8601 format
alert(d.toISOString());
// will output as UTC in an implementation dependent format
alert(d.toGMTString());
// will output as UTC in a locale specific format
alert(d.toLocaleString(undefined, {timeZone: 'UTC'}));
}
This question already has answers here:
JavaScript's getDate returns wrong date
(12 answers)
Closed 6 years ago.
This is so basic, but it makes no sense to me:
new Date("2010-01-01").getFullYear();
result: 2009
wth? My goal is to reformat the date as mm/dd/yyyy given the format yyyy-mm-dd..
Adding on:
new Date("2010-01-01").getMonth();
result: 11
new Date("2010-01-01").getDate();
result: 31
The date string you're passing into new Date() has no timezone in it. It's being interpreted as UTC. The critical thing to understand here is that a Date is stored as a Unix timestamp (seconds since 1970-01-01 00:00, making 'Date' a misleading name) so if you don't specify the time within the date, it's going to apply a default.
Date.prototype.getFullYear() retrieves the full year for that timestamp in your LOCAL time. (See the docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getFullYear)
You're somewhere west of UTC, and 2010-01-01 UTC is 2009-12-31 in your local time.
And for your final mystery....getMonth() is 0-based, not 1-based, so '11' is December.
Don't use the Date constructor to parse strings, it's largely implementation dependent and inconsistent.
An ISO 8601 date and time without a timezone like "2016-02-29T12:00:00" should be treated as local (i.e. use the host system timezone offset to create a Date), but a date–only string is treated like "2016-02-29" as UTC. The first behaviour is consistent with ISO 8601, but the second isn't.
Some versions of browsers will treat date–only strings as UTC, and some as invalid dates, so always parse strings manually (a two line function or library can help). That way you know how it will be parsed in all hosts.
Provide Time with date in the new Date() as parameter . Then u will get exact Result.
A leading zero for the day within a string seems to break the Javascript Date object in Chrome. There are also some inconsistencies between browsers, since Firefox handles the leading zero correctly, but fails when the zero is not included. See this example: https://jsfiddle.net/3m6ovh1f/3/
Date('2015-11-01'); // works in Firefox, not in Chrome
Date('2015-11-1'); // works in Chrome, not in Firefox
Why? Is there a good way to work around/with the leading zero?
Please note, the strings are coming from MySQL via AJAX and all dates will contain the leading zero, and I can fix this by formating the dates server-side. What format would work the best?
EDIT
Just to specify what my problem was, it looks like Chrome is applying a time zone to the YYYY-MM-DD format, which reverts the Nov. 1st date back to the Oct. 31st date (because of my EDT local time).
According to ECMA-262 (5.1):
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.
The date/time string format as described in 15.9.1.15 is YYYY-MM-DDTHH:mm:ss.sssZ. It can also be a shorter representation of this format, like YYYY-MM-DD.
2015-11-1 is not a valid date/time string for Javascript (note it's YYYY-MM-D and not YYYY-MM-DD). Thus, the implementation (browser) is able to do whatever it wants with that string. It can attempt to parse the string in a different format, or it can simply say that the string is an invalid date. Chrome chooses the former (see DateParser::Parse) and attempts to parse it as a "legacy" date. Firefox seems to choose the latter, and refuses to parse it.
Now, your claim that new Date('2015-11-01') doesn't work in Chrome is incorrect. As the string conforms to the date/time string format, Chrome must parse it to be specification compliant. In fact, I just tried it myself -- it works in Chrome.
So, what are your options here?
Use the correct date/time format (i.e. YYYY-MM-DD or some extension of it).
Use the new Date (year, month, date) constructor, i.e. new Date(2015, 10, 1) (months go from 0-11) in this case.
Whichever option is up to you, but there is a date/time string format that all specification compliant browsers should agree on.
As an alternative, why not use unix timestamps instead? In JavaScript, you would would multiply the timestamp value by 1000,
e.g
var _t = { time: 1446220558 };
var _d = new Date( _t.time*1000 );
Test in your browser console:
new Date( 14462205581000 );
// prints Fri Oct 30 2015 11:55:58 GMT-0400 (EDT)
There's a little benefit in it as well (if data comes via JS) - you'd save 2 bytes on every date element '2015-10-30' VS 1446220558 :)
If I use new Date('2015-01-01'), will that give me a point in time equivalent to 2015-01-01T00:00:00Z?
From documentation:
constructors: ... dateString String value representing a date. The
string should be in a format recognized by the Date.parse() method
(IETF-compliant RFC 2822 timestamps and also a version of ISO8601).
From documentation on Date.parse():
If a time zone is not specified and the string is in an ISO format
recognized by ES5, UTC is assumed
However, depending on this behaviour could be risky, as, from the same document:
note that ECMAScript ed 6 draft specifies that date time strings without a time zone are to be treated as local, not UTC