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.
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:
new Date()
) — "2025-02-18T14:30:00.000Z"
1732561800000
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.
Date
objectThe 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.
Date
object methodsNative 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!!!)
Date
formatting methodsNot 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.
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
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"
While this approach works, it quickly gets complex when you consider:
For anything beyond simple formats, it’s time to bring out the big guns.
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.
In my opinion, date-fns is the best choice for modern applications.
date-fns is:
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
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.
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 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:
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
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:
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.
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 | NativeDate |
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 |
Date
methods — Small projects, simple date displays, and when bundle size is criticalWhen selecting a date library, performance implications should factor into your decision, especially for date-heavy applications:
Each library adds weight to your application:
Date
— 0kb (built into JavaScript)Date
methods — Fastest but limited in functionalityDate
objects, which are memory-efficient and perform wellHere’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
Date
objects with ==
or ===
(use .getTime()
instead)//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 / }
JavaScript date formatting doesn’t have to be a headache. The key is choosing the right tool for your specific needs:
Date
methods — Work well for simple use cases and offer good performanceWhen in doubt, start simple and only reach for more complex solutions when needed. Your bundle size and application performance will thank you!
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowWalk you through how to set up and use the Tailwind Typography plugin, also known as the @tailwindcss/typography or the prose plugin.
TypeScript adds static typing to JavaScript code, which helps reduce unpredictable behavior and bugs. In the past, TypeScript code couldn’t […]
Implement secure authentication and role-based authorization in Astro using JWT, SSR, and Astro middleware for protected routes.
Walk through how to use Google Fonts and locally installed fonts in your Tailwind projects to help you improve your project typography and design consistency.