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

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(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. Remember that months are zero-indexed in JavaScript's Date API
- Be careful with date math across DST boundaries
- Don't directly compare
Date
objects with==
or===
(use.getTime()
instead) - 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).