Jay Gould

Dates and times with vanilla Javascript - the easy way

May 14, 2021

Time in Javascript

Using libraries like Moment.js is great, but they add considerable overhead and bulk to your application. This bulk can be worth the trade off if your app requires complicated date manipulation, but I find most of the time the date requirements for me can easily be handled with a few lines of custom date code.

This cheat sheet will show some basic date manipulation, and also provide approaches for common and yet more complex usage of date and time with pure, vanilla Javascript.

Difference between “Date()” and “new Date()”

They both log something like Sat Mar 20 2021 20:38:30 GMT+0000 (Greenwich Mean Time), but the difference is that:

  • Date() returns the above date in a string format
  • new Date() returns a date object, which can be used to perform various date operations

Getting dates in to the standard Date() object

When formatting dates, the goal is to get the date or string representation of a date in to the JS Date() object. From here, the JavaScript date constructor can format the date for us, which will be covered later. First though, the next few points will discuss getting the date into the date object.

Formatting current date

Formatting the current date is simple as the new Date() object can be used:

const today = new Date()

const defaultFormatted = today.toLocaleDateString("en-GB")

console.log(defaultFormatted) // 20/03/2021

const customFormatted = today.toLocaleDateString("en-GB", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric",
})

console.log(customFormatted) // Saturday, 20 March 2021

Formatting custom date

It’s sometimes required to take an existing date string and convert it to a different date format. If we have control over the date we want to format, a date string in the format 2021-03-27 can be passed as an argument to Date():

let date = new Date("2021-03-27")

console.log(date) // Sat Mar 27 2021 00:00:00 GMT+0000 (Greenwich Mean Time)

You can then perform other date formatting with toLocaleDateString as mentioned above.

Formatting database date

This is common when working with dates stored in a database such as 2021-03-20 07:17:10.192+00.

The new Date() constructor accepts the argument in the local format (“2021-03-27” as mentioned above), and also in the format of the number of milliseconds since Jan 1970.

Getting the number of seconds since Epoch:

console.log(Math.round(Date.now())) // 1616275999801

Another way of getting the number of seconds since Epoch is using Date.parse(). This takes a date string and returns the Epoch time. This can be combined with the new Date() to get the built in Date object representation of the date:

const custom = new Date(Date.parse("2021-03-25 07:17:10.192+00"))
const customFormatted = custom.toLocaleDateString("en-GB")

console.log(customFormatted) // 25/03/2021

Date.parse() accepts the following date formats:

  • “1/1/1970”
  • “1/1/1970 12:00:00 AM”
  • “Thu Jan 01 1970”
  • “Thu Jan 01 1970 00:00:00”
  • “Thu Jan 01 1970 00:00:00 GMT-0500”
  • “1970-01-01 00:00:00.000+00”

Note: MDN states that “parsing of strings was entirely implementation dependent” until recently, so the dates can be parsed in different ways. I’ve never had a problem with .parse(), but it looks like it will start being dodgy if you pass in slightly off dates or ones from different timezones. If you’re always passing in from reliable strings from a database “datetime” field, you’ll be fine.

Formatting a custom date structure

For the most part, the options above should be enough to get what is required for dates. Occasionally though, a random and non-useful date structure needs to be converted for use with the Date constructor. For example, a date such as 2021 Jan 20th, when added to the Date() or Date.parse() functions will not convert correctly because they’re not expecting that format. This must manually be deconstructed and added back in to the date object:

const weirdDate = `2021-January-20`

const yearInteger = weirdDate.substring(0, 4)
const dayInteger = weirdDate.substring(9, 11)

const month = weirdDate.substring(5, 8)
const months = {
  January: 0,
  February: 1,
  // etc.
}
const monthInteger = months[month]

Now the raw elements are obtained and converted to integers they are passed in to the date object. This is the other way the arguments of the Date() can be used:

const date = new Date(yearInteger, monthInteger, dayInteger)

console.log(date) // 20/01/2021

This can be time consuming, and is a great reason to reach for a library like Moment, as the existing date format can be passed in and used so Moment knows how to interpret the date:

moment("20/1/2021", "YYYY/MMM/DD")

Adding and subtracting from a date

Adding and subtracting from a date is one of those things in Javascript that can be done in hundreds of different ways. I like to keep it simple:

let originalDate = new Date("2023-03-27")
const numberOfDaysToAdd = 6
originalDate.setDate(originalDate.getDate() + numberOfDaysToAdd)

console.log(originalDate) // Sun Apr 02 2023 01:00:00 GMT+0100 (British Summer Time)

This approach mutates the original date object. If this is not something you want, here’s an immutable way:

let originalDate = new Date("2023-03-27")
const numberOfDaysToAdd = 6

let datePlusDays = new Date(originalDate)
datePlusDays.setDate(datePlusDays.getDate() + numberOfDaysToAdd)

console.log(originalDate) // Mon Mar 27 2023 01:00:00 GMT+0100 (British Summer Time)
console.log(datePlusDays) // Sun Apr 02 2023 01:00:00 GMT+0100 (British Summer Time)

The difference between two dates

It doesn’t seem like this would need to be calculated that often, but I’ve had to do this many times. It’s especially useful when showing on an application how long ago something happened, or how long until something happens.

const millisecondsPerDay = 1000 * 60 * 60 * 24

const date1 = new Date("2023-03-27")
const date2 = new Date("2023-08-21")

const date1Raw = Date.UTC(
  date1.getFullYear(),
  date1.getMonth(),
  date1.getDate()
)
const date2Raw = Date.UTC(
  date2.getFullYear(),
  date2.getMonth(),
  date2.getDate()
)

const differenceInDays = Math.floor((date2Raw - date1Raw) / millisecondsPerDay)

console.log(differenceInDays + "days") // 147 days

The above approach strips out the time and locality information with the UTC method, leaving us with the Epoch time mentioned earlier in milliseconds. The number of days is extracted from this by dividing by the number of milliseconds in a day.

The difference between two times

The difference between two times is similar to above, but we’re dealing with seconds, minutes and hours rather than days.

const date1 = new Date("2023-03-27 12:30:40")
const date2 = new Date("2023-03-27 13:29:40")

const secondsPerDay = 1000
const minutesPerDay = 1000 * 60
const hoursPerDay = 1000 * 60 * 60

const differenceInSeconds = Math.floor((date2 - date1) / secondsPerDay)
const differenceInMinutes = Math.floor((date2 - date1) / minutesPerDay)
const differenceInHours = Math.floor((date2 - date1) / hoursPerDay)

console.log(differenceInSeconds + "seconds") //  3540 seconds
console.log(differenceInMinutes + "minutes") // 59 minutes
console.log(differenceInHours + "hours") // 0 hours

The above approach is simple, but it’s probably too simple to be used for an impressive real life application feature. It shows the seconds OR minutes OR hours, but in reality we’d want an approach that tells us the number of days AND hours AND minutes AND seconds. This more complicated approach is the sort of thing you’ll see on a social post, where the post was added “1 min ago” or “3 days ago” for example, being flexible enough to show any date/time format depending on when it was posted.

The date and time difference between two date times

As mentioned above, this approach is a little more complicated because we need to account for seconds, minutes, hours, and days depending on the time difference.

function timeBetween(date1, date2) {
  let secondsBetween = (date2 - date1) / 1000

  let interval = secondsBetween / 31536000 // seconds in a year
  if (interval > 1) {
    return Math.floor(interval) <= 1
      ? Math.floor(interval) + " year between"
      : Math.floor(interval) + " years between"
  }
  interval = secondsBetween / 2592000 // seconds in a month
  if (interval > 1) {
    return Math.floor(interval) <= 1
      ? Math.floor(interval) + " month between"
      : Math.floor(interval) + " months between"
  }
  interval = secondsBetween / 86400 // seconds in a day
  if (interval > 1) {
    return Math.floor(interval) <= 1
      ? Math.floor(interval) + " day between"
      : Math.floor(interval) + " days between"
  }
  interval = secondsBetween / 3600 // seconds in an hour
  if (interval > 1) {
    return Math.floor(interval) <= 1
      ? Math.floor(interval) + " hour between"
      : Math.floor(interval) + " hours between"
  }
  interval = secondsBetween / 60 // seconds in a minute
  if (interval > 1) {
    return Math.floor(interval) <= 1
      ? Math.floor(interval) + " minute between"
      : Math.floor(interval) + " minutes between"
  }

  return secondsBetween <= 60
    ? "Just now"
    : Math.floor(secondsBetween) + " seconds between"
}

const date1 = new Date("2021-04-27")
const date2 = new Date("2021-04-29")

console.log(timeBetween(new Date("2021-04-27"), new Date("2021-04-29"))) // 2 days between
console.log(timeBetween(new Date("2021-02-29"), new Date("2021-04-12"))) // 1 month between
console.log(timeBetween(new Date("2020-04-29"), new Date("2023-07-12"))) // 3 years between

The above approach calculates how many seconds are in years, months, days etc.. and if it’s been more than 1 day/hour/month it will move to the next smallest time until it reaches one which reaches a whole number. Perfect for working out a human readable representation of date/time difference.

Similarly, the above function can be changed to calculate time since the current date/time, which might be more useful for calculating time difference for something like a social post:

function timeSince(date) {
  let seconds = Math.floor((new Date() - date) / 1000)

  let interval = seconds / 31536000 // seconds in a year
  if (interval > 1) {
    return Math.floor(interval) <= 1
      ? Math.floor(interval) + " year ago"
      : Math.floor(interval) + " years ago"
  }
  interval = seconds / 2592000 // seconds in a month
  if (interval > 1) {
    return Math.floor(interval) <= 1
      ? Math.floor(interval) + " month ago"
      : Math.floor(interval) + " months ago"
  }
  interval = seconds / 86400 // seconds in a day
  if (interval > 1) {
    return Math.floor(interval) <= 1
      ? Math.floor(interval) + " day ago"
      : Math.floor(interval) + " days ago"
  }
  interval = seconds / 3600 // seconds in an hour
  if (interval > 1) {
    return Math.floor(interval) <= 1
      ? Math.floor(interval) + " hour ago"
      : Math.floor(interval) + " hours ago"
  }
  interval = seconds / 60 // seconds in a minute
  if (interval > 1) {
    return Math.floor(interval) <= 1
      ? Math.floor(interval) + " minute ago"
      : Math.floor(interval) + " minutes ago"
  }

  return seconds <= 60 ? "Just now" : Math.floor(seconds) + " seconds ago"
}

console.log(timeSince(new Date("2020-04-29")) // This will be how ever many days/months/years from the current time!

Thanks for reading!


Senior Engineer at Haven

© Jay Gould 2023, Built with love and tequila.