Search code examples
javascripthtmldatetimezonelocale

Initialize JS date object from HTML date picker: incorrect date returned


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.


Solution

  • 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'}));
    }