How to format dates in JavaScript: Methods, libraries, and best practices

Written by Paul Akinyemi✏️ JavaScript date handling presents challenges that can impact application reliability. This guide examines native Date API capabilities alongside specialized libraries, providing practical examples and performance metrics to inform your implementation decisions. You'll learn when to use built-in methods versus external libraries, how to properly handle localization and time zones, and how to avoid common date-related pitfalls in your projects. Understanding date formats Before formatting a date for display, you need to understand the format in which you receive it. Here are the three most common formats, each of which displays the same date and time: ISO 8601 strings (standardized and directly consumable by new Date()) — "2025-02-18T14:30:00.000Z" Unix timestamps (numeric representation that is timezone-agnostic) — 1732561800000 RFC 2822 — Tue, 18 Feb 2025 14:30:00 +0000 Each format has its place depending on context. ISO 8601 is the most common for APIs and databases because it is standardized and easily parsed by new Date(). Since they're just raw numbers, Unix timestamps are great for calculations and comparisons. RFC 2822 is mostly seen in older systems or emails. Regardless of the format you start with, JavaScript’s Date object is your primary tool for interpreting and working with these values. The JavaScript Date object The Date object is JavaScript’s built-in way to work with dates and times. Here’s what you need to know: // Creating a new Date object const now = new Date(); // Current date and time const isoDate = new Date('2025-02-18T14:30:00.000Z'); // From date string const withComponents = new Date(2025, 1, 18); // Year, month (0-indexed!), day const timeStampDate = new Date(1732561800000) Tip: JavaScript months are zero-indexed (0 = January, 11 = December). The Date object stores dates as milliseconds since Thursday, January 1, 1970 (Unix epoch), but provides methods to work with them in human-readable formats. Common Date object methods Native methods let you extract parts of the date. For example: const date = new Date('2025-02-18T14:30:15Z'); // Getting components date.getFullYear(); // 2025 date.getMonth(); // 1 (February, zero-indexed!!!!) date.getDate(); // 18 date.getHours(); // 14 date.getMinutes(); // 30 date.getSeconds(); // 15 date.getDay(); // 2 (Tuesday, with 0 being Sunday) date.getTime(); // Milliseconds since epoch // Setting components date.setFullYear(2026); date.setMonth(5); // June (because zero-indexed!!!) Native Date formatting methods Not every scenario calls for a full-featured library; sometimes it’s like using a sledgehammer to crack a nut. In many cases, JavaScript’s built-in date formatting methods are more than sufficient: const date = new Date('2025-02-18T14:30:00Z'); // Basic string conversion date.toString(); // "Tue Feb 18 2025 14:30:00 GMT+0000 (Coordinated Universal Time)" // Date portion only date.toDateString(); // "Tue Feb 18 2025" // Time portion only date.toTimeString(); // "14:30:00 GMT+0000 (Coordinated Universal Time)" // UTC version (reliable across timezones) date.toUTCString(); // "Tue, 18 Feb 2025 14:30:00 GMT" // ISO 8601 format date.toISOString(); // "2025-02-18T14:30:00.000Z" These native methods provide a quick way to format dates without any extra dependencies. They're perfect for simple use cases like displaying UTC values or splitting out the date or time. Locale-aware formatting with toLocaleDateString() const date = new Date('2025-02-18'); // Basic usage (uses browser's locale) date.toLocaleDateString(); // In US: "2/18/2025" // In UK: "18/02/2025" // In Germany: "18.2.2025" // With explicit locale date.toLocaleDateString('fr-FR'); // "18/02/2025" // With options const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; date.toLocaleDateString('de-DE', options); // "Dienstag, 18\. Februar 2025" No locales, no options: date.toLocaleDateString(); // 2/18/2025 DIY date formatting with native JavaScript Sometimes you need to implement a custom date formatting solution. This approach gives you granular control over the output format and allows for optimization based on specific use cases: function formatDate(date, format) { const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); // Replace tokens with actual values return format .replace('YYYY', year) .replace('MM', month) .replace('DD', day) .replace('HH', hours) .replace('mm', minutes) .replace('ss', seconds); } const date = new Date('2025-02-18T14:30:45Z'); console.log(fo

May 12, 2025 - 16:29
 0
How to format dates in JavaScript: Methods, libraries, and best practices

Written by Paul Akinyemi✏️

JavaScript date handling presents challenges that can impact application reliability. This guide examines native Date API capabilities alongside specialized libraries, providing practical examples and performance metrics to inform your implementation decisions.

You'll learn when to use built-in methods versus external libraries, how to properly handle localization and time zones, and how to avoid common date-related pitfalls in your projects.

Understanding date formats

Before formatting a date for display, you need to understand the format in which you receive it. Here are the three most common formats, each of which displays the same date and time:

  • ISO 8601 strings (standardized and directly consumable by new Date())"2025-02-18T14:30:00.000Z"
  • Unix timestamps (numeric representation that is timezone-agnostic)1732561800000
  • RFC 2822Tue, 18 Feb 2025 14:30:00 +0000

Each format has its place depending on context. ISO 8601 is the most common for APIs and databases because it is standardized and easily parsed by new Date(). Since they're just raw numbers, Unix timestamps are great for calculations and comparisons. RFC 2822 is mostly seen in older systems or emails. Regardless of the format you start with, JavaScript’s Date object is your primary tool for interpreting and working with these values.

The JavaScript Date object

The Date object is JavaScript’s built-in way to work with dates and times. Here’s what you need to know:

// Creating a new Date object
const now = new Date(); // Current date and time
const isoDate = new Date('2025-02-18T14:30:00.000Z'); // From date string
const withComponents = new Date(2025, 1, 18); // Year, month (0-indexed!), day
const timeStampDate = new Date(1732561800000)

Tip: JavaScript months are zero-indexed (0 = January, 11 = December).

The Date object stores dates as milliseconds since Thursday, January 1, 1970 (Unix epoch), but provides methods to work with them in human-readable formats.

Common Date object methods

Native methods let you extract parts of the date. For example:

const date = new Date('2025-02-18T14:30:15Z');

// Getting components
date.getFullYear(); // 2025
date.getMonth(); // 1 (February, zero-indexed!!!!)
date.getDate(); // 18
date.getHours(); // 14
date.getMinutes(); // 30
date.getSeconds(); // 15
date.getDay(); // 2 (Tuesday, with 0 being Sunday)
date.getTime(); // Milliseconds since epoch

// Setting components
date.setFullYear(2026);
date.setMonth(5); // June (because zero-indexed!!!)

Native Date formatting methods

Not every scenario calls for a full-featured library; sometimes it’s like using a sledgehammer to crack a nut. In many cases, JavaScript’s built-in date formatting methods are more than sufficient:

const date = new Date('2025-02-18T14:30:00Z');

// Basic string conversion
date.toString(); 
// "Tue Feb 18 2025 14:30:00 GMT+0000 (Coordinated Universal Time)"

// Date portion only
date.toDateString(); 
// "Tue Feb 18 2025"

// Time portion only
date.toTimeString(); 
// "14:30:00 GMT+0000 (Coordinated Universal Time)"

// UTC version (reliable across timezones)
date.toUTCString(); 
// "Tue, 18 Feb 2025 14:30:00 GMT"

// ISO 8601 format
date.toISOString(); 
// "2025-02-18T14:30:00.000Z"

These native methods provide a quick way to format dates without any extra dependencies. They're perfect for simple use cases like displaying UTC values or splitting out the date or time.

Locale-aware formatting with toLocaleDateString()

const date = new Date('2025-02-18');

// Basic usage (uses browser's locale)
date.toLocaleDateString(); 
// In US: "2/18/2025"
// In UK: "18/02/2025"
// In Germany: "18.2.2025"

// With explicit locale
date.toLocaleDateString('fr-FR'); 
// "18/02/2025"

// With options
const options = { 
  weekday: 'long', 
  year: 'numeric', 
  month: 'long', 
  day: 'numeric' 
};
date.toLocaleDateString('de-DE', options); 
// "Dienstag, 18\. Februar 2025"

No locales, no options:

date.toLocaleDateString();
// 2/18/2025

DIY date formatting with native JavaScript

Sometimes you need to implement a custom date formatting solution. This approach gives you granular control over the output format and allows for optimization based on specific use cases:

function formatDate(date, format) {
  const day = String(date.getDate()).padStart(2, '0');
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const year = date.getFullYear();
  const hours = String(date.getHours()).padStart(2, '0');
  const minutes = String(date.getMinutes()).padStart(2, '0');
  const seconds = String(date.getSeconds()).padStart(2, '0');

  // Replace tokens with actual values
  return format
    .replace('YYYY', year)
    .replace('MM', month)
    .replace('DD', day)
    .replace('HH', hours)
    .replace('mm', minutes)
    .replace('ss', seconds);
}

const date = new Date('2025-02-18T14:30:45Z');
console.log(formatDate(date, 'YYYY-MM-DD')); // "2025-02-18"
console.log(formatDate(date, 'DD/MM/YYYY HH:mm:ss')); // "18/02/2025 14:30:45"

When native methods don’t suffice

While this approach works, it quickly gets complex when you consider:

  • Localization (month names, day names)
  • Different calendar systems
  • Time zones
  • AM/PM formatting

For anything beyond simple formats, it’s time to bring out the big guns.

Modern library approaches

Native methods can be too limiting, especially if you have to deal with complex localization, custom formatting, or timezone manipulations. In such cases, popular libraries can help. Let’s look at some popular date formatting libraries.

date-fns: The functional approach

In my opinion, date-fns is the best choice for modern applications. date-fns is:

  • Tree-shakable — Only import what you need
  • Immutable — Functions don't modify original dates
  • Well-documented and TypeScript-friendly — Both major positives

Let's see how date-fns simplifies common tasks like parsing ISO strings into Date objects, formatting them into readable strings, and performing date math like adding days or finding the difference between two dates. Its functional design keeps things clean, predictable, and easy to chain together:

import { format, parseISO, addDays, differenceInDays } from 'date-fns';

// Parsing
const date = parseISO('2025-02-18T14:30:00Z');

// Formatting
format(date, 'yyyy-MM-dd'); // "2025-02-18"
format(date, 'MMMM do, yyyy'); // "February 18th, 2025"
format(date, 'h:mm a'); // "2:30 PM"
format(date, 'EEEE, MMMM do, yyyy h:mm a'); // "Tuesday, February 18th, 2025 2:30 PM"

// Operations
const nextWeek = addDays(date, 7);
const daysBetween = differenceInDays(nextWeek, date); // 7

Localization with date-fns

date-fns provides robust localization support through separate locale imports. This modular approach keeps your bundle size minimal by only including the locales you need:

import { format, formatDistance, formatRelative, isDate } from 'date-fns'; 
import { es, de, fr, ja, zhCN } from 'date-fns/locale';

const date = new Date('2025-02-18T14:30:00Z'); 

// Basic locale formatting 
const localeExamples = { 
  english: format(date, 'MMMM d, yyyy', { locale: enUS }), 
  spanish: format(date, 'MMMM d, yyyy', { locale: es }), 
  german: format(date, 'MMMM d, yyyy', { locale: de }), 
  french: format(date, 'MMMM d, yyyy', { locale: fr }), 
  japanese: format(date, 'MMMM d, yyyy', { locale: ja }), 
  chinese: format(date, 'MMMM d, yyyy', { locale: zhCN }) 
};

console.log(localeExamples); 
 Output: 
  { 
   english: "February 18, 2025", 
   spanish: "febrero 18, 2025", 
   german: "Februar 18, 2025", 
   french: "février 18, 2025", 
   japanese: "2月 18, 2025", 
   chinese: "二月 18, 2025" 
 }

If you need more customization or have edge cases to handle, check the documentation for additional techniques and examples.

Timezone handling with date-fns-tz

date-fns-tz extends date-fns with robust timezone handling. It allows converting and formatting dates across time zones. Let’s explore its key features:

import { 
  format, 
  utcToZonedTime, 
  zonedTimeToUtc, 
  getTimezoneOffset 
} from 'date-fns-tz'; 

const date = new Date('2025-02-18T14:30:00Z');

// Basic timezone conversion 
const timezoneExamples = { 
  newYork: utcToZonedTime(date, 'America/New_York'), 
  tokyo: utcToZonedTime(date, 'Asia/Tokyo'), 
  london: utcToZonedTime(date, 'Europe/London'), 
  sydney: utcToZonedTime(date, 'Australia/Sydney') 
};

// console.log(timezoneExamples)

//{
//  newYork: Tue Feb 18 2025 09:30:00 GMT-0500 (Eastern Standard Time),
//  tokyo:   Tue Feb 18 2025 23:30:00 GMT+0900 (Japan Standard Time),
//  london:  Tue Feb 18 2025 14:30:00 GMT+0000 (Greenwich Mean Time),
//  sydney:  Wed Feb 19 2025 01:30:00 GMT+1100 (Australian Eastern Daylight Time)
//}

Day.js: A lightweight Moment.js alternative

Day.js has gained significant popularity as a modern, minimalist alternative to Moment.js. It was explicitly designed to address Moment's shortcomings while maintaining a similar API, making it an excellent choice for migration projects.

Here's an example demonstrating how to set up Day.js with useful plugins for working with timezones, custom formats, and locales. We see how to create date objects from different input types, format them into readable strings, convert them to a specific timezone, and perform date math like adding or subtracting time—all while keeping the original dates immutable:

import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import localeData from 'dayjs/plugin/localeData';
import customParseFormat from 'dayjs/plugin/customParseFormat';

// Extend with plugins
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(localeData);
dayjs.extend(customParseFormat);

// Creating Day.js objects
const today = dayjs();
const specificDate = dayjs('2025-02-18');
const fromFormat = dayjs('18/02/2025', 'DD/MM/YYYY');

// Formatting
specificDate.format('YYYY-MM-DD'); // "2025-02-18"
specificDate.format('dddd, MMMM D, YYYY'); // "Tuesday, February 18, 2025"

// Timezone handling
specificDate.tz('America/New_York').format('YYYY-MM-DD HH:mm:ss Z'); 
// "2025-02-18 09:30:00 -05:00"

// Manipulation (immutable - returns new instances)
const nextWeek = specificDate.add(1, 'week');
const lastMonth = specificDate.subtract(1, 'month');

Key advantages of Day.js include:

  • Tiny footprint ~2KB minified and gzipped (core only)
  • Familiar API Similar to Moment.js for easier migration
  • Immutable — All operations return new objects
  • Plugin-based Only include the features you need
  • Chainable API Convenient method chaining

The plugin architecture of Day.js is really nice. You only pay the size cost for features you actually use:

// Only import the plugins you need
import relativeTime from 'dayjs/plugin/relativeTime';
import calendar from 'dayjs/plugin/calendar';
dayjs.extend(relativeTime);
dayjs.extend(calendar);

// Now you can use these features
dayjs('2025-02-18').fromNow(); // "in X years" (depends on current date)
dayjs('2025-02-18').calendar(); // "02/18/2025" or "Tuesday" based on how far in future

Moment.js: The legacy option

While Moment.js was once the go-to library for date handling in JavaScript, it is now considered legacy. The Moment.js team has officially declared the library in maintenance mode and recommends newer alternatives. That said, many existing projects still use it, so it's worth understanding its approach.

Let's look at a typical Moment.js workflow: creating date objects from strings or custom formats, formatting them into readable outputs, adjusting dates by adding or subtracting time, and converting them to specific time zones. It's a practical example of how Moment was commonly used in real-world applications before more modern libraries took the lead:

import moment from 'moment';
import 'moment-timezone';

// Creating moments
const now = moment(); // Current date/time
const fromString = moment('2025-02-18T14:30:00Z');
const fromFormat = moment('18/02/2025', 'DD/MM/YYYY');

// Formatting
fromString.format('YYYY-MM-DD'); // "2025-02-18"
fromString.format('dddd, MMMM Do YYYY'); // "Tuesday, February 18th 2025"
fromString.format('h:mm a'); // "2:30 pm"

// Operations (modifies the original moment)
fromString.add(7, 'days');
fromString.subtract(2, 'months');

// Timezone handling
const tokyoTime = fromString.clone().tz('Asia/Tokyo').format('YYYY-MM-DD HH:mm:ss');
const nyTime = fromString.clone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss');

Moment's main drawbacks include:

  • Not tree-shakable — Large bundle size
  • Mutable API — Moments can be accidentally modified
  • Complex chainable API — can lead to unexpected behavior

Temporal: The future of JavaScript date handling

The ECMAScript Temporal proposal aims to replace the problematic Date API with a more comprehensive, immutable, and timezone-aware solution. While not yet standardized, it's worth keeping an eye on as it represents the future of date handling in JavaScript. Here’s a snippet that showcases Temporal’s modern approach: creating immutable, timezone-aware dates and performing safe arithmetic:

// This syntax is not yet available in browsers without polyfills

// Creating a date (Temporal.PlainDate is timezone-independent)
const date = Temporal.PlainDate.from({ year: 2025, month: 2, day: 18 });

// Creating a specific time in a timezone
const nyDateTime = Temporal.ZonedDateTime.from({
  timeZone: 'America/New_York',
  year: 2025, month: 2, day: 18, hour: 9, minute: 30
});

// Formatting
date.toString(); // "2025-02-18"
nyDateTime.toString(); // "2025-02-18T09:30:00-05:00[America/New_York]"

// Duration and arithmetic (returns new instances)
const futureDate = date.add({ days: 7 });
const duration = date.until(futureDate);

You can experiment with Temporal using the polyfill available at npmjs.com/package/@js-temporal/polyfill.

Library comparison and selection guide

There are many options to choose from, so it’s understandable if making a decision is hard. Here's a comparison table to help you make an informed decision:

Feature Native `Date` date-fns Day.js Moment.js
Bundle size 0 KB 13 KB 2 KB 67 KB
Immutability No Yes Yes No
Tree-shaking N/A Excellent Good Poor
Timezone support Basic Via date-fns-tz Via plugin Via plugin
Localization Good Excellent Good Excellent
TypeScript support Basic Excellent Good Via DefinitelyTyped
Learning curve Moderate Low Low Moderate
Active development Slow Active Active Maintenance only

When to use each option

  • Native Date methods Small projects, simple date displays, and when bundle size is critical
  • date-fns Modern applications, when functional programming is preferred, or when tree-shaking is important
  • Day.js When migrating from Moment.js, or when minimal bundle size is crucial but native methods aren't enough
  • Moment.js Legacy applications, or when migration costs outweigh benefits

Performance considerations

When selecting a date library, performance implications should factor into your decision, especially for date-heavy applications:

Bundle size impact

Each library adds weight to your application:

  • Native Date — 0kb (built into JavaScript)
  • date-fns — ~13kb minified + gzipped (core functions only)
  • date-fns with all locales — ~500kb (before tree-shaking)
  • Day.js — ~2kb minified + gzipped (core only)
  • Day.js with plugins — ~5-10kb depending on plugins used
  • Moment.js — ~67kb minified + gzipped
  • Moment.js with all locales — ~302kb

Memory and CPU usage

  • Native Date methods — Fastest but limited in functionality
  • date-fns — Uses pure functions that create new Date objects, which are memory-efficient and perform well
  • Day.js — Excellent performance for basic operations, but can slow down with heavy plugin usage
  • Moment.js — Creates wrapped objects that consume more memory and can be slower in processing-intensive operations

Here's a simplified comparison:

// Test with 100,000 operations
const COUNT = 100000;

// Native JS
console.time('Native');
for (let i = 0; i < COUNT; i++) {
  new Date().toISOString();
}
console.timeEnd('Native'); // Typically fastest

// date-fns
console.time('date-fns');
for (let i = 0; i < COUNT; i++) {
  format(new Date(), 'yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\'');
}
console.timeEnd('date-fns'); // Close second

// Day.js
console.time('Day.js');
for (let i = 0; i < COUNT; i++) {
  dayjs().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]');
}
console.timeEnd('Day.js'); // Usually faster than Moment

// Moment.js
console.time('Moment');
for (let i = 0; i < COUNT; i++) {
  moment().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]');
}
console.timeEnd('Moment'); // Usually slowest

Avoiding common pitfalls

  1. 1. Remember that months are zero-indexed in JavaScript's Date API
    1. Be careful with date math across DST boundaries
    2. Don't directly compare Date objects with == or === (use .getTime() instead)
    3. Watch out for unexpected parsing behavior with different browsers:
>//Incorrect: Direct comparison
        const date1 = new Date('2025-02-18');
        const date2 = new Date('2025-02-18');
        if (date1 === date2) { / This will never execute / }// Correct version: Compare timestamps
        if (date1.getTime() === date2.getTime()) { /*This works / }```



# Conclusion

JavaScript date formatting doesn't have to be a headache. The key is choosing the right tool for your specific needs:

*   **Native** `Date` **methods** — Work well for simple use cases and offer good performance
*   **date-fns** — Gives you a modern, functional approach with excellent tree-shaking
*   **Moment.js** — Still works, but is no longer recommended for new projects
*   **Temporal API** — Represents the future of JavaScript date handling

When in doubt, start simple and only reach for more complex solutions when needed. Your bundle size and application performance will thank you!

---

##[LogRocket](https://lp.logrocket.com/blg/javascript-signup?utm_source=devto&utm_medium=organic&utm_campaign=25Q2&utm_content=javascript-date-format): Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.

[LogRocket](https://lp.logrocket.com/blg/javascript-signup?utm_source=devto&utm_medium=organic&utm_campaign=25Q2&utm_content=javascript-date-format) allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.

[LogRocket](https://lp.logrocket.com/blg/javascript-signup?utm_source=devto&utm_medium=organic&utm_campaign=25Q2&utm_content=javascript-date-format) records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

[Try it for free](https://lp.logrocket.com/blg/javascript-signup?utm_source=devto&utm_medium=organic&utm_campaign=25Q2&utm_content=javascript-date-format).