I'm trying to compare two dates in javascript which are formatted differently. Namely:
2015-09-30T00:00:00 and 9/30/2015 12:00:00 AM
The former is UTC and the latter is not in UTC format. Logically, I am referring to the same date/time here but I can't come up with a way to compare them that will return true. Creating a new Date object with each ends up with different results (due to UTC offset with my local time zone).
What am I missing?
Ended up concluding that tagging on a "UTC" to the second date format before creating the Date object leads to an output consistent with the UTC formatted date.
Related
I have two dates on the client
openTime: 2020-01-01 00:00:000 (GMT+2)
closeTime: 2020-01-01 06:00:000 (GMT+2)
Before sending it to the server I convert them to ISO
openTime.toISOString() // 2019-12-31T22:00:00.000Z
closeTime.toISOString() // 2020-01-01T04:00:00.000Z
Server receives these dates and applies validation logic
isSameDay(openTime, closeTime) // false
The comparison result is false. It happens because of timezones. Whenever openTime with such time converts to UTC the date "jumps" into the different date.
Before converting to ISO
openTime: 2020-01-01 00:00:000
After converting to ISO
openTime: 2019-12-31T22:00:00.000Z
How to solve this issue?
Is it okay to send client timeZoneOffset beside openTime & closeTime values.
..or, send openTime & closeTime with custom format which adds timezone to the ISO string?
FWIW, I'd go with your concept of formatting the date with the timezone in it. Make the timezone part +HH:MM or -HH:MM (four digits) and the JavaScript Date object will be able to parse it correctly. (That's part of the ISO 8601 subset that JavaScript supports.) So for instance, 2021-08-02T16:03:26.499+02:00.
Then, in your server-side, to see whether the open and close are on the same day, you can just use string comparison: open.substring(0, 10) === close.substring(0, 10).
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'}));
}
I have dates stored in my database in UTC format and calling
element.createdDate = new Date(element.createdDate.toString());
results in displaying the wrong date.
calling
element.createdDate = new Date(element.createdDate.toUTCString());
returns nothing. How do I go about displaying the correct time from UTC?
It appears that your json response contains a string valued which are in ISO8601 format in UTC, and then you are creating Date objects from them.
This part of your code is fine:
if (element.createdDate) element.createdDate = new Date(element.createdDate.toString());
You parse the string, and the resulting Date object is correct.
However, you don't need to use .toString() here, as the value is already a string. That is redundant.
This part of your code is the problem:
console.log("javascript date: " + new Date(element.depositDate.getUTCDate().toString()));
The getUTCDate function returns just the date of the month. Don't use that.
No matter what you do to create the Date object, ultimately you create a Date object and you're relying upon an implicit string conversion to output it. This will have different behavior in different browsers.
Consider console.log(new Date()):
In Chrome, this logs something like Fri Mar 17 2017 12:14:29 GMT-0700 (Pacific Daylight Time) on my computer. This is as if I called console.log(new Date().toString()); It is in an RFC 2822 like format (but not quite), and is represented in local time.
In Firefox, this logs something like 2017-03-17T19:14:46.535Z. This is as if I called console.log(new Date().toISOString()); It is in ISO8601 format, and is represented in UTC.
The point is, don't rely on implicit undefined behavior. If you must work with Date objects, then you should use console.log(element.createdDate.toISOString()) to see the ISO8601 representation of the UTC time.
If you're going to be doing a lot of things with dates and times, you may prefer to use a library, such as Moment.js, which can make tasks such as this more clear.
I have dates stored in my database in UTC format and calling
element.createdDate = new Date(element.createdDate.toString());
results in displaying the wrong date.
2016-10-11T00:00:00Z and Mon Oct 10 2016 20:00:00 GMT-04:00 (EDT) are exactly the same moment in time. The only difference is that one is displayed in ISO 8601 extended format with timezone offset 00:00 and the other is displayed in an RFC 2822 (like) format with timezone offset -04:00 (and assumes a locality in the EDT region).
calling
element.createdDate = new Date(element.createdDate.toUTCString());
returns nothing.
That is unusual. Typically it would return either a string or an error, but without a working example or any code to provide context it's impossible to say why.
How do I go about displaying the correct time from UTC?
You haven't specified what "correct" is. You are displaying a date and time for the same moment in time, just in a different format and time zone.
I am using a jQuery UI datepicker to show a range of dates that are available to the user. I get the date ranges from an API in the form of
"DateRanges": [
{
"StartDate": "0001-01-01T00:00:00",
"EndDate": "0001-01-01T00:00:00"
}
]
The problem is that I am running a comparison on the dates in the beforeShowDay API mapping function, but when I try to convert these dates into a JavaScript Date object for comparison, the Date() constructor is converting the dates to local time and therefore causing the date to change.
For example, if I use new Date("2014-08-22"); then I would expect a Date object that was set to 8/22/2014 at 12:00 AM but instead it is showing 8/21/2014 at 5:00 PM PST. Therefore my comparison does not work correctly because of the conversion.
What can I do to force the Date() object to not change to local time when making comparisons?
This happens because the parsing logic defaults to the UTC time zone when you do not supply one. To get the expected time, pass a time zone.
new Date("2014-08-22T00:00:00-0700");
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse
This is another tidbit from the docs that might help (depending on how you are generating those dates):
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).
so you could also use:
new Date("September 11, 2014");
and you will get a date in your local tz (for me the output is: "Thu Sep 11 2014 00:00:00 GMT-0500 (CDT)").