Back to blog overview

January 4, 2024

How to Convert ISO Strings to Local Time with Vanilla JavaScript

Sunjay Armstead

&

&

UI/UX Designer and Engineer II
Louisburg, NC

Handling dates in JavaScript is notoriously difficult. You have to account for local timezones, daylight savings implications, the grand “epoch” of 1970, and milliseconds conversions. Does your head hurt too?

In a recent code challenge for Advent of JavaScript 2023, I was trying to figure out how many days and weeks from the current Date a particular holiday party was. My initial calculations were frustratingly inaccurate, but I uncovered a few tricks that I hope will help you too.

Credit: Amy Dutton, Advent of JavaScript 2023

## Expected Payload

The Secret Santa app code examples for Advent of JavaScript 2023 are set up using RedwoodJS. As such, form data is managed behind the scenes with React Hook Form and dates are sent to the server in ISO date format:

```json

// Expected date payload from React Hook Form

2023-12-23T00:00:00.000Z

```

Hold up! Don’t run away. Let’s break this string down:

  • <code>2023</code> is the event year.
  • <code>12</code> is the event month.
  • <code>23</code> is the event date.
  • <code>T</code> indicates the beginning of the timestamp.
  • <code>00:00:00.000</code> are the hours, minutes, seconds, and milliseconds respectively. All of those zeros indicates a time of midnight, or the very beginning of the day.
  • <code>Z</code> is the timezone offset, specifically the UTC offset. This value could have been set to the local timezone (for example <code>-05:00</code> for the Eastern Standard Time timezone), but React Hook Form sets it to <code>+00:00</code> or, simply, <code>Z</code>.
  • Given this insight, we can use the following placeholder to remember ISO date string format: <code>YYYY-MM-DDTHH:mm:ss.sssZ</code>. You can read more about this format on Mozilla’s “Date time string format” documentation.

**An important note:** my application specifically uses the `<DateField>` component from React Hook Form which selects a date at midnight UTC +00:00. If we used the `<DatetimeLocalField />` component from React Hook Form instead (which uses `type="datetime-local"` for its input element), it would send a date at a specific time selected by the user adjusted to their local timezone.

## Quirks of Local Time

By far the easiest way figure out the time between two dates is to compare them both in terms of the same timezone. This, however, is where things start to take a left turn.

Take `Date.now()` or `new Date()`, for example. These methods return dates adjusted to your computer’s timezone. For me that is EST, but it may be different for other users.

The main issue here is that if we are comparing the current date adjusted for a user’s timezone and a date not adjusted for their timezone (i.e. the date of the holiday event stored in the application), our results will likely be inaccurate.

For instance: Saturday, 08:00 PM EST (UTC -05:00) is actually Sunday, 01:00 AM GMT (UTC +00:00). So if you’re trying to calculate the days until an event, you are likely to be over or under by a day, depending on your local timezone.

It would, therefore, be best for the application to convert stored UTC +00:00 dates to the local time first. But how do you do that?

## Swing and a Miss

JavaScript comes with some powerful `Date` APIs, but they are unfortunately unpredictable at times and can be challenging to use.

Say, for instance, that a user chose an event date of December 23, 2023. React Hook Form will send the ISO string `2023-12-23T00:00:00.000Z`, since we are using its `<DateField>` component (see “Expected Payload” section above). This is ******mostly****** correct in that the year, month, and date are what the user expects. The timestamp, however, may not match the user’s timezone like we need it to in order to properly perform Date calculations.

Credit: Amy Dutton, Advent of JavaScript 2023

We could try to take the ISO string and transform it into a date object, which automatically converts it to our timezone:

```jsx

// Expected date payload from React Hook Form
const payload = '2023-12-23T00:00:00.000Z'

const payloadAsDate = new Date(payload)
console.log(payloadAsDate) // Returns 'Fri Dec 22 2023 19:00:00 GMT-0500 (Eastern Standard Time)'

```

Uh oh, did you see what just happened? Our `payloadAsDate` object returns the 22nd of December, not the 23rd!  While technically correct due to timezone conversions, this is not what the user expects to see as their event date.

What can we do to convert the stored date to midnight local time on the correct day?

Some Good News

Thankfully, ISO date strings are standardized and we can expect the same pattern every time. What if we could pull out the year, month, and day from our payload and ignore the timezone? Let’s give it a shot with String methods:

```jsx

const payload = '2023-12-23T00:00:00.000Z'
const payloadSplit = payload.split('-') // Returns an array of substrings, one substring before each '-' character
const payloadYear = payloadSplit[0]
const payloadIndexedMonth = payloadSplit[1] - 1
const payloadDate = payloadSplit[2].split('T')[0]

// Use the payload's year, month, and day to create a new Date object, set to midnight at local time
const payloadToLocalDate = new Date(
 payloadYear,
 payloadIndexedMonth,
 payloadDate,
 0,
 0,
 0,
 0
)

console.log(payloadToLocalDate) // Returns 'Sat Dec 23 2023 00:00:00 GMT-0500 (Eastern Standard Time)'

```

Huzzah!  Our current date from `new Date()` and our payload date are in the same timezone. Now, how do we compare the two dates programmatically?

How to Compare Dates

First up, we need to convert our current date to midnight to make for more sensible comparisons. We will take an approach similar to what we did when converting the payload to local time at midnight. We are only doing this since our users are not concerned with an event time; they simply want to see how many days and weeks remain until their upcoming party.

```jsx

const today = new Date()
const thisYear = today.getFullYear()
const thisDate = today.getDate()
const thisMonth = today.getMonth()
const todayAtMidnight = new Date(thisYear, thisMonth, thisDate, 0, 0, 0, 0)

```

Next, lets convert both of these dates to milliseconds to perform mathematical operations that we can’t do with Strings. For our example, let’s assume that the current date is December 22, 2023 and that the event date is December 23, 2023:

```jsx

const todayMs = todayAtMidnight.getTime() // Returns 1703221200000 ms
const payloadMs = payloadToLocalDate.getTime() // Returns 1703307600000 ms

```

By taking the difference between the two, we can determine how much time in milliseconds it is until our event. To then get the number of days remaining, we can divide the milliseconds remaining by the total number milliseconds in a day:

```jsx

const secToMs = 1000
const minToMs = 60 * secToMs
const hourToMs = 60 * minToMs
const dayToMs = 24 * hourToMs

const msUntil = payloadMs - todayMs // Returns 86400000 ms
const totalDaysUntil = msUntil / dayToMs // Returns 1

```

A Step Further

Now let’s go one step further. Our users want to see the time remaining in terms of weeks and days. The number of weeks makes more sense as an Integer and not a Float (e.g. a number with decimals). So let’s divide our `totalDaysUntil` by 7 (the total number of days in a week) and round down.

```jsx

const weeksUntil = Math.floor(totalDaysUntil / 7)

```

If `totalDaysUntil / 7` is a float, the numbers after the decimal demonstrate that a certain fraction of a week remains in addition to the total weeks before the decimal. Floats are yucky and, thankfully, JavaScript gives us the ability to find the remainder of days as an Integer using its remainder operator.

```jsx

const weeksUntil = Math.floor(totalDaysUntil / 7)
const daysRemaining = totalDaysUntil % 7

```

So, imagine that `totalDaysUntil` is 9. If we divide those 9 days by the total days in a week of 7, the quotient represents the number of weeks (1) and the remainder (2) represents the number of days. Give your brain a high five!

Is there Another Way to Do This?

By this point you may be wondering why you’d approach a problem like this with Vanilla JS. After all, it is a lot work to do it this way.

One reason I like this exploration is that it forces me to stare the date monster square in the eyes 👾. Dates are really cumbersome in JavaScript and I am always curious how things work under the hood.

The question remains, though, about other approaches to handling and comparing dates in JavaScript. Here are a few ones that come to mind:

  • You could use libraries like date-fns or even Moment.js that come with handy methods to breeze through some of the problems we faced in this article.
  • You could manipulate the date data on the client before it gets saved to the database so that it reflects the user’s timezone (thanks Vitaly Tikhoplav for this idea).
  • You could experiment with the Intl API (thanks Jesse House for your thoughts on this), or even the currently proposed Temporal API.
  • You could also change the database column data type to a String instead of DateTime and simply save the dates without a timestamp (thanks Aaron Spjut for this idea).

Obviously, there are quite a few ways to approach dates in JavaScript. Whichever option you choose, I encourage you to understand what’s happening behind the scenes so that you can get a better sense of how dates are handled in JavaScript.

Photo by Jon Tyson on Unsplash

Let's Chat

Are you ready to build something brilliant? We're ready to help.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
RedwoodJS Logo
RedwoodJS
Conference

conference
for builders

Grants Pass, Oregon • September 26 - 29, 2023
View All