The problem with most of the answers here, including Diego's answer, is that it will run into day boundary conversions on certain timezone + time combinations. i.e. you want to get today's date at midnight, but because the system is set to a negative offset timezone, it will will get tomorrow's date (or vice versa -- will get yesterday's date if your system is set to a postive offset timezone).
Pedro Castilho's answer has the right idea, but it's actually not correct for all timezones + time combinations, and it's only by happenstance for the combinations where it is correct.
Before we get to the correct implementation of Pedro's answer, let's cover our assumptions and prove our assertions:
First off, it's important to remember that timezone offsets are always added to UTC time in order to arrive at the local time. Think about this with a simple example: When it's 12pm in London, it's 1pm in Paris. The timezone offset for Paris is +1, so in formula this is expressed as:
Local Time = UTC Time + Offset
UTC Time = Local Time - Offset
Secondly, when we call JS new Date().toISOString()
, JS gets the current system time, the current system timezone setting (can be confirmed by checking Intl.DateTimeFormat().resolvedOptions().timeZone
property in JS console), and uses that info to convert to UTC time (because all JS objects are stored as UTC), and does not convert the result back to local time. So in our example, if we're on a system set to exactly June 1 at midnight in Paris timezone, calling new Date().toISOString()
will return 2023-05-31T23:00:00.000Z
Thirdly, what the questioner wants is the formatting of the JS Date.toISOString()
, but without conversion to UTC timezone -- i.e. 2023-06-01T00:00:00.000
. Since all JS date objects are stored in UTC natively, the most straightforward way to do this, is to simply allow the conversion by JS to UTC to take place, then undo it.
In order to undo the conversion, we must start from a consistent base -- i.e. will not change depending on system timezone and/or locale settings. The reliable choice here is Date.getTime
. When we call new Date().getTime()
, it will return the number of seconds since the epoch, and those seconds are just a flat integer -- with no reference to timezone. The timezone is implicitly assumed to be UTC, and all of JS methods operate off this assumption. Since we want to undo the UTC conversion that has already happened when JS created the Date object, we need to use the formula for local time, but taking its inverse i.e. we need to subtract back the offset that was previously added. So we obtain the following formulas:
Local Time = UTC Time - Offset
UTC Time = Local Time + Offset
When we put all the above together, we can come up with a very general-purpose method that converts a JS Date Object to an "ISO string" but formatted in the local timezone:
// jsDateToISOLocalStr
// Converts a JS date to a "ISO-style" string (i.e. not converting to UTC Zero Offset Time)
// i.e. Suppose it is May 2, 2023 @ 4:36 AM, and we are on a system with a +10 timezone (around Australia)
// The difference between 'toISOString' builtin and jsDateToISOLocalStr is simply as thus:
// new Date().toISOString() --> '2023-05-01T18:36:31.866Z'
// jsDateToISOLocalStr() --> '2023-05-02T04:36:31.866'
// All JS Date objects are stored as UTC, and so if a local timezone is displayed by JS, we must "convert back"
// params:
// * d (optional) -- anything that can be parsed by JS new Date constructor
// returns -- One of the below:
// * if d can be parsed by JS Date: an ISO-style string representation of d according to local (machine) time
// * if d is not truthy or not passed in: an ISO-style string representation of 'right now' according to local time
// * if d cannot be parsed by JS date (that is, when new Date(d) returns "Invalid Date"): null
export const jsDateToISOLocalStr = (d) => {
let retVal = null;
// We necessarily do some string parsing because JS Date doesn't parse dates consistently
// e.g. -- new Date("2023-05-01") returns midnight at UTC,
// new Date("2023-05-01 ") (or any non-strict variant) returns midnight in the local timezone
// We will force to the local time unless any time component is already in the string
// If the total string length is less than 11, then there isn't any time component.
const parsedInput = (typeof d === "string" || d instanceof String) && d.length < 11 ? `${d} ` : d;
// We must not pass a date argument if we want today's date. "new Date(null)" returns the epoch (Jan 1 1970)!
const utcDate = d ? new Date(parsedInput) : new Date();
if (String(utcDate).toLowerCase() !== "invalid date") {
// Conversion to UTC already took place -- now we must *undo* that
const localTimestamp = utcDate.getTime() - utcDate.getTimezoneOffset() * 60 * 1000;
const localDate = new Date(localTimestamp);
// Remove the final trailing 'Z' to indicate local time
retVal = localDate.toISOString().slice(0, -1);
}
return retVal;
};
Now we can finally get the desired output:
const isoLocalDateTime = jsDateToISOLocalStr();
const isoLocalDate = isoLocalDateTime.split("T")[0];
// To write the local date set to midnight time
document.write(`${isoLocalDate}T00:00:00.000`);
// Optionally, to write the local date set to the current time
document.write(isoLocalDateTime);
// Optionally, to just write the local date with no time component
document.write(isoLocalDate);
toISOString()
, you accepted an answer that adjusts the date for the local timezone, which makes timezone handling unclear. – Laure