Is your JavaScript Date one day off?

Sungbin Kim
4 min readSep 16, 2023

--

When I built my first website in JavaScript, I noticed that some dates are one day off. After debugging why, I discovered that using JavaScript Date is trickier than I expected. With the emergence of a back-end JavaScript like Node.js, JavaScript is becoming a popular choice of programming language for full stack development, so I believe more developers will encounter this problem. I will summarize my findings here for all the future developers who might wonder why their dates are one day off.

Many websites have forms that include a date input, which looks like

The value of a date input is a string in the format YYYY-MM-DD, e.g. "2023-09-15" , which is the ISO 8601 format. Whenever you store the value of a date input in a database column of type Date, you need to convert a string to a Date object. During this conversion is when the date becomes one day off.

For example, suppose the value of a date input is "2023-09-15" , which is then converted to a Date object by new Date("2023-09-15") . If this Date object is printed to an user in New York, they will see "2023-09-14" . The reason is that the time zone of New York is UTC-4, which is four hours behind Coordinated Universal Time (UTC).

Time zones matter to JavaScript Date. The official documentation states:

When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as local time.

“date-only forms” refer to strings in the format YYYY-MM-DD, and “date-time forms” refer to strings in the format YYYY-MM-DDTHH:mm:ss.

Here is what happens when I convert each of these strings to a Date object in New York:

> new Date("2023-09-15")
< Thu Sep 14 2023 20:00:00 GMT-0400 (Eastern Daylight Time) {}
> new Date("2023-09-15T00:00:00")
< Fri Sep 15 2023 00:00:00 GMT-0400 (Eastern Daylight Time) {}

We expect the output to be the same because we think the input is the same. However, to JavaScript, the input is not the same because "2023-09-15" is a date in UTC and "2023-09-15T00:00:00" is a date in UTC-4.

However, users intend “date-only forms” to be local dates. If an user in New York entered "2023-09-15" in a date input, they meant "2023-09-15" in UTC-4, not UTC. Therefore, developers often need to correct the default behavior of JavaScript Date. Here are some solutions that I found:

Solution without using a library

Found in https://stackoverflow.com/questions/7556591/is-the-javascript-date-object-always-one-day-off

const dateOnlyRegex = /^([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])))$/

function parseDateString(dateString) {
if (dateOnlyRegex.test(dateString)) {
const utcDate = new Date(dateString)
const localDate = new Date(utcDate.getTime() + utcDate.getTimezoneOffset() * 60000)
return localDate
}
return new Date(dateString)
}

If dateString is not in the date-only form, this function simply does new Date(dateString) because it works as expected in all other cases, some of which are:

> parseDateString("2023-09-15T00:00:00") // date-time form without time zone offset
< Fri Sep 15 2023 00:00:00 GMT-0400 (Eastern Daylight Time) {}
> parseDateString("2023-09-15T04:00:00Z") // date-time form in UTC
< Fri Sep 15 2023 00:00:00 GMT-0400 (Eastern Daylight Time) {}
> parseDateString("2023-09-15T00:00:00-04:00") // date-time form with time zone offset
< Fri Sep 15 2023 00:00:00 GMT-0400 (Eastern Daylight Time) {}

The only edge case is when dateString is in the date-only form, when we need to interpret it as a local date, not an UTC date.

> new Date("2023-09-15")
< Thu Sep 14 2023 20:00:00 GMT-0400 (Eastern Daylight Time) {} // Wrong
> parseDateString("2023-09-15")
< Fri Sep 15 2023 00:00:00 GMT-0400 (Eastern Daylight Time) {} // Correct

Solution using date-fns-tz library

date-fns-tz is a library that is built upon date-fns , which is the second most popular JavaScript Date utility library after Moment.js . date-fns does not support time zones besides UTC and the system time zone, but date-fns-tz does.

import { toDate } from "date-fns-tz"

const dateTimeRegex = /^([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9](:([0-5][0-9]|60))?(\.[0-9]{1,9})?)?)?(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00)?)?)?$/

function parseDateString(dateString) {
if (dateTimeRegex.test(dateString)) {
return toDate(dateString)
}
return new Date(dateString)
}

Instead of manually adding a time zone offset, all we need to do is use toDate library function. However, it supports only the dates in the ISO 8601 format, so use new Date() constructor for all other dates.

In this article, I summarized why some JavaScript dates are one day off and how to correct them. In the next article, I will write about the challenge of working with three time zones: system, UTC and remote!

--

--

Sungbin Kim

Sungbin is a founding engineer at a healthcare startup based in New York. He graduated from Columbia University in 2021 and worked at Epic from 2021 to 2023.