loading...
Cover image for Formatting dates with JavaScript

Formatting dates with JavaScript

jesseskinner profile image Jesse Skinner Originally published at codingwithjesse.com Updated on ・6 min read

There are a number of popular JavaScript date formatting libraries out there, such as Moment.js, Luxon and date-fns. Those libraries are very powerful and useful, allowing you to request various date formats using a special syntax, but they also come with many more features than most people will ever use. And that means, your users are probably downloading more JavaScript then they need to.

In this article, I'm going to show you how to use the built in basic Date object to format dates without any third-party library. In other words, we'll be formatting dates with pure vanilla JavaScript.

Feel free to copy and paste the solutions below to use as a starting point in your own code bases. I'll demonstrate how to generate a number of common format types, but you may need to modify the solutions below a little bit to format dates and times to be exactly the way you want.

What about the Internationalization API?

Before I start, I should mention that there is some formatting functionality built into JavaScript dates, using the Internationalization API.

Using the Internationalization API, you can format dates according to a specific locale, which means formatting according to the customs of the user's location and language. If you're not picky about how dates and times will be displayed, this can work well in many cases, but it depends on each user's operating system, and which locales are installed on their devices. In other words, it can be hard to predict what the format will look like in any given browser.

If you want to format dates in some specific way and have full control over what is being displayed, please read on.

Date methods

Pretty much all the information we need can be provided by a few built-in methods on the date object:

const date = new Date; // current time & date

date.getFullYear(); // Year
date.getMonth(); // Month of the year 0-11 (0 = January)
date.getDate(); // Date of the month, 1-31
date.getDay(); // Day of the week, 0-6 (0 = Sunday)
date.getHours(); // Hours, 0-23
date.getMinutes(); // Minutes, 0-59
date.getSeconds(); // Seconds, 0-59

Now you may have noticed that all these methods return numbers. But how are you supposed to get words out of it like "Thursday" or "November"? And what if you want your month or date number to start with a zero? No problem, we can use JavaScript!

Years

Get the full year

Getting the year out of the Date object is really easy, but it's a four-digit year by default:

date.getFullYear(); // returns 2019

What if you want only two-digits?

There is a getYear() function in the Date object as well, and sometimes I accidently use that instead of getFullYear(). However, it's more or less useless. In 2019, it returns 119. Why?? Because there is a Y2K bug baked into JavaScript, even though JavaScript was designed in 1995! In those first five years of JavaScript, people could call getYear() for a two-digit year, and simply add 1900 to get a four-digit year. And I guess that still works, because 1900 + 119 = 2019!

Since the getYear() function has been broken since the year 2000, I recommend getting a two-digit year using this approach instead:

function getTwoDigitYear(date) {
    return date.getFullYear() % 100; // returns 19
}

Months

Display the month as a two-digit number

The getMonth() function of the Date object returns a number between 0 and 11. That has got to be one of the biggest surprises when working with dates in JavaScript. It also mostly makes this method useless without writing more code. Let's see how to do that.

function getTwoDigitMonth(date) {
    // add one to month to make it 1-12 instead of 0-11
    const month = date.getMonth() + 1;

    if (month < 10) {
        // add a 0 to the start if necessary
        return `0${month}`;
    } else {
        // for 10, 11 and 12, just return the month
        return month.toString();
    }
}

Display the month as a string

If we want to display the month as a string of text like "February" or "Mar", then we need to use a JavaScript array with all the months. In fact, this is why the getMonth() method returns a number between 0 and 11, because arrays start counting at 0 as well!

function getMonthName(date) {
    const months = [
        'January',
        'February',
        'March',
        'April',
        'May',
        'June',
        'July',
        'August',
        'September',
        'October',
        'November',
        'December'
    ];

    return months[date.getMonth()];
}

If you want to use a short form of the month, or just a single character, or another language, you can easily adapt the code above to change the contents of the array with whatever you prefer to use.

Days of the week

If you're going to be displaying the day of the week, you'll probably want to be displaying some text. You can use the same approach that we used for formatting months above. Basically, you just need to define an array of text and access it using the getDay() result as an index.

function getWeekDayName(date) {
    // make sure you start with Sunday
    const weekDays = [
        'Sunday',
        'Monday',
        'Tuesday',
        'Wednesday',
        'Thursday',
        'Friday',
        'Saturday'
    ];

    return weekDays[date.getDay()];
}

Again, if you want to return different text, like 'Sun' or 'S', just replace the entries in the array with whatever you prefer.

Day of the month

The day of the month is pretty straightforward. You can just call getDate() to get the number, and you don't have to add one to it or anything.

Display the day of the month as a two-digit number

For some date formats, you may want to get a two-digit version of the date number. That's simple enough:

function getTwoDigitDayOfTheMonth(date) {
    const dayOfTheMonth = date.getDate();

    if (dayOfTheMonth < 10) {
        // add a 0 to the start if necessary
        return `0${dayOfTheMonth}`;
    } else {
        // for 10 or greater, just return the dayOfTheMonth
        return dayOfTheMonth.toString();
    }
}

Display an ordinal with the day of the month

If you want a fancy day of the month with an ordinal after it, like 1st, 2nd, 3rd, 4th, 21st, etc., you can easily figure that out with a switch statement:

function getDayOfTheMonthWithOrdinal(date) {
    const dayOfTheMonth = date.getDate();
    const ordinal = getOrdinal(dayOfTheMonth);

    return `${dayOfTheMonth}${ordinal}`;
}

function getOrdinal(number) {
    // using the % modulo operator to get the last digit of the number
    const lastDigitOfNumber = number % 10;

    switch (lastDigitOfNumber) {
        case 1:
            return 'st';
        case 2:
            return 'nd';
        case 3:
            return 'rd';
        default:
            return 'th';
    }
}

Times

You can apply the techniques we used above with times as well, depending what you need. Let's say we want to display a time format in 12-hour time with "am" or "pm", like "9:45pm". Here's how:

function formatTwelveHourTime(date) {
    // call functions below to get the pieces we need
    const hour = getHourInTwelveHourClock(date);
    const minute = getTwoDigitMinute(date);
    const meridiem = getMeridiem(date);

    // put it all together
    return `${hour}:${minute}${meridiem}`;
}

function getHourInTwelveHourClock(date) {
    const hour = date.getHours();

    if (hour === 0 || hour === 12) {
        return 12;
    }

    // otherwise, return a number between 1-11
    return hour % 12;
}

function getTwoDigitMinute(date) {
    const minute = date.getMinutes();

    if (minute < 10) {
        // add a 0 to the start if necessary
        return `0${minute}`;
    } else {
        // for 10 or greater, just return the minute
        return minute.toString();
    }
}

function getMeridiem(date) {
    const hour = date.getHours();

    if (hour < 12) {
        return 'am';
    } else {
        return 'pm';
    }
}

Bringing it all together

We've covered how to generate all the different pieces of various date formats. How about putting all the difference pieces together?

You can use any method you like, but I suggest an approach like the following, using a template string.

function shortDateFormat(date) {
    // use the built-in function here
    const year = date.getFullYear();

    // use the functions we wrote above
    const month = getTwoDigitMonth(date);
    const dayOfTheMonth = getTwoDigitDayOfTheMonth(date);

    // put it all together, eg. "YYYY-MM-DD"
    return `${year}-${month}-${dayOfTheMonth}`;
}

You can create as many formatting functions as you have formats to generate. Here's another example:

function longDateTimeFormat(date) {
    const weekDayName = getWeekDayName(date);
    const monthName = getMonthName(date); 
    const dayOfTheMonth = getDayOfTheMonthWithOrdinal(date);
    const year = date.getFullYear();
    const time = formatTwelveHourTime(date);

    // put it together, eg. "Friday, April 19th, 2019 at 9:45pm"
    return `${weekDayName}, ${monthName} ${dayOfTheMonth}, ${year} at ${time}`;
}

Conclusion

I hope I've provided you with all the tools and techniques you might need to format dates and times. You can apply many of these techniques in other ways too, like formatting currencies. More importantly, with a little bit of custom JavaScript, you can avoid having additional dependencies for your project and downloads for your users.


Interested in web development? Subscribe to the Coding with Jesse newsletter!

Discussion

pic
Editor guide
Collapse
moopet profile image
Ben Sinclair

I know a lot of people look for a generic .format() method on dates, and you could knock something up fairly quickly like this (incomplete):

function formatDate(format, d) {
  d = d || new Date();

  const days = [
    'Sunday',
    'Monday',
    'Tuesday',
    'Wednesday',
    'Thursday',
    'Friday',
    'Saturday'
  ];

  const dateFormats = {
    'Y': d => d.getYear() + 1900,
    'm': d => ('0' + (d.getMonth() + 1)).slice(-2),
    'j': d => d.getDate(),
    'd': d => ('0' + d.getDate()).slice(-2),
    'H': d => d.getHours(),
    'i': d => ('0' + d.getMinutes()).slice(-2),
    's': d => d.getSeconds(),
    'A': d => d.getHours() < 12 ? 'AM' : 'PM',
    'a': d => d.getHours() < 12 ? 'am' : 'pm',
    'l': d => days[d.getDay()],
    'D': d => days[d.getDay()].slice(0, 3),
    'S': d => {
      switch (d.getDate() % 10) {
        case 1: return 'st';
        case 2: return 'nd';
        case 1: return 'rd';
        default: return 'th';
      }
    }
  };

  let output = '';
  let escapeNext = false;

  for (index in format) {
    const letter = format[index];

    if (escapeNext) {
      output += letter;
      escapeNext = false;
      continue;
    }

    if (letter == '\\') {
      escapeNext = true;
      continue;
    }

    if (dateFormats.hasOwnProperty(letter)) {
      output += dateFormats[letter](d);
    }
    else {
      output += letter;
    }
  }


  return output;
}


console.log(formatDate('Y-m-d H:i'));
// 2019-04-21 12:45
console.log(formatDate('l (jS) \\a\\t H:i A'));
// Sunday (21st) at 12:45

That's not i18n or anything, and I just borrowed some of the date format from other languages (cough the PHP manual cough) but it's probably pretty similar to what a lot of these libraries sweep under the rug.

Collapse
qcgm1978 profile image
Youth

I code some tests for this article: repl.it/repls/WhirlwindRigidMemory...
I found toLocaleString() method has some effect. e.g. the test not passed if I don't code like this:

let date = new Date(new Date(1555861886106)
.toLocaleString("en-US", { timeZone: "Asia/Shanghai" }));
Collapse
jesseskinner profile image
Jesse Skinner Author

Had a second look, seems it's the timeZone that you added that's making the tests pass. I plan on covering timezones in a future article - maybe in the next article about manipulating dates, or maybe I'll save it for its own article since time zones in the browser can be such a headache.

Collapse
jesseskinner profile image
Jesse Skinner Author

That's interesting. Yeah I found the locale stuff to be rather unpredictable. Sometimes that's okay but I usually prefer to have more control over it.

Collapse
jesseskinner profile image
Jesse Skinner Author

That's amazing! Thanks for putting this together!

Collapse
karataev profile image
Eugene Karataev

Many times I started projects without dates libraries dependencies. Formatting a date as described in your article is fine without a library. But when it comes to manipulating with dates like adding, subtracting, calculating duration - libraries are a real timesaver.
Thanks for your article, we really should keep in mind that date formatting can be done without serving users bunch of unnecessary JS. But I think that if you know from the start that your project will heavily use date calculations, then npm install a date library without a doubt.

Collapse
jesseskinner profile image
Jesse Skinner Author

It's funny you should say that, because my next article was going to be about manipulating dates without a library 😉

Collapse
karataev profile image
Eugene Karataev

Oh, I'll definitely take a look at your next article to check how you manipulate with dates with vanilla javascript.

Collapse
daveskull81 profile image
dAVE Inden

Great article. I appreciate the approach to formatting dates using vanilla Javascript and not reaching for a library right away. Dates in Javascript can be hard enough to deal with that reaching for a library is easy to do. This is a good reminder, not only for Dates but really anything we are coding, to look into seeing if we can build something on our own without the need for a library. Using a library should add real value to your project to ensure you are getting real benefits from it. I'm looking forward to your article on manipulating dates without a library. :)

Collapse
jesseskinner profile image
Jesse Skinner Author

Awesome, thanks for the feedback! I'm looking forward to writing the next article too :)

Collapse
rhymes profile image
rhymes

Hi Jesse, I truly believe your intentions are good and it's a great article as an insight about how formatting dates might work in practices but I have to disagree with some of the wording.

I'm definitely in favor of using simpler solution instead of bringing in a full library to solve a seemingly simple problem but I'm going to play the devil's advocate here and say this: dates are not a simple problem. Which is why we end up with these many libraries (too many TBH :D).

If you're not picky about how dates and times will be displayed, this can work well in many cases, but it depends on each user's operating system, and which locales are installed on their devices

Sure, unless you use the same locale for each user, which is basically what you're doing here:

function getMonthName(date) {
    const months = [
        'January',
        'February',
        'March',
        'April',
        'May',
        'June',
        'July',
        'August',
        'September',
        'October',
        'November',
        'December'
    ];

    return months[date.getMonth()];
}

I'm not an English native speaker and I live in a country of non native English speakers, so I have to take into account that if I'm working a UI for the local market displaying September won't cut it.

I might harcode my version of the list of months, but as you surely know, building untranslatable apps can give you headaches down the line if you ever have to cater more than one market. But even if you don't, I'm 100% sure you'll find people in each country that have a locale set different from the one tied to the language spoken by the majority (and there are countries, including Canada that have more than one official locale).

So whatever the app is built in, I have to think about the possibility that I might need to display the name of the month in more than one language.

With your example I could hard code all the possible lists or choose a set of "supported languages" but the Intl API gives me that for free (without a library!), why not use it?

> new Intl.DateTimeFormat('default', {"month": "long"}).format(new Date())
"April"
> new Intl.DateTimeFormat('fr-CA', {"month": "long"}).format(new Date())
"avril"

If you really want to make sure that everyone is seeing a month name in English you can switch default (as you see I keep my browser in English even if it's not my first language) to en-US, en-CA or equivalents.

We could even make a case that respecting the user's wishes (how they want the dates/times to be formatted) also improves the user's experience.

I hope I've provided you with all the tools and techniques you might need to format dates and times.

You're seemingly encouraging people (correct if I'm wrong about this) to give away with internationalization features that are builtin in browsers that took so much time to be finally standardized so that they can use a set of custom made functions? This is the part I disagree with the most. You brushed them aside quickly and I fear that someone down the line will read the paragraph "What about the Internationalization API?" and dismiss the whole API because your functions are simple to understand and well documented thanks to this article.

I truly think you have good intentions and I enjoyed your explanation of the shortcomings of the builtin JS functions (one day I'll write something about the stupid shortcomings of Python's functions eheh) but I think your article should make clear that Internationalization it's an important issue, even if you don't actually plan to use it in the beginning, and that these are good examples of how date formatting might work, not "all the tools you might need".

Collapse
jesseskinner profile image
Jesse Skinner Author

You're right, the Internationalization API would probably be the preferred approach in most cases. This article is showing how to do it manually if that's what you choose or prefer to do.

Collapse
funkybob profile image
Curtis Maloney

Thorough coverage!

Reminds me of something I started building for a client for a similar purpose, but as we had limited needs, I never fleshed it out as much.

Am curious why you used the if/else for adding the leading 0s, and not:

d.getDate().toString().padStart(2, '0')

Only IE doesn't support padStart...

Collapse
jesseskinner profile image
Jesse Skinner Author

No particular reason. padStart works well for this.

Collapse
elizabethschafer profile image
Elizabeth Schafer

This is great, thanks for sharing! It's easy to reach for a date library without thinking about how much bulk it adds, or if it's even necessary.

Collapse
jesseskinner profile image
Jesse Skinner Author

Glad you enjoyed it! I totally agree, I think it's too easy to reach for a library when solving a problem, but we need to weigh the impact on the page and the user.