The Right Way to Handle Dates and Times in JavaScript
Dates in JavaScript are notoriously painful. Here's what actually works for common date tasks without losing your mind to timezone bugs.
JavaScript's Date object is one of the original 1995 design decisions the language still carries. Months starting at 0, inconsistent string parsing, limited timezone support. The good news: modern approaches have made date handling manageable, and the Temporal API will eventually replace Date entirely.
The Timezone Rule
Store dates in UTC. Display in local time. This single rule prevents 80% of timezone bugs. When you store '2026-03-15T14:00:00Z' (the Z means UTC), there's no ambiguity. When you display it, convert to the user's local timezone. Never store 'March 15, 2026 at 2 PM' without a timezone โ that's an accident waiting to happen.
For Simple Use Cases: date-fns
date-fns is a collection of pure functions for date manipulation. Tree-shakeable, so you only import what you use. Immutable โ functions return new Date objects rather than mutating the input. The API is predictable: format(date, 'yyyy-MM-dd'), addDays(date, 7), isAfter(date1, date2). For most applications without complex timezone requirements, date-fns covers everything you need.
For Timezone-Aware Work: Luxon or the Temporal Polyfill
Luxon was created by the Moment.js team as the modern replacement. It handles IANA timezones correctly, supports locale-aware formatting, and has an immutable API. For scheduling applications, multi-timezone event systems, or any feature where 'what time is it in Tokyo right now' needs an accurate answer, Luxon or the Temporal polyfill are the right tools.
The native Date Parsing Problem
new Date('2026-03-15') gives you midnight UTC, which converts to March 14 in timezones behind UTC. new Date('2026/03/15') gives you midnight local time. Different string formats behave differently. The safe approach: always parse ISO 8601 strings (2026-03-15T00:00:00Z) explicitly or use a library. Never rely on new Date(someString) without knowing the format.
Formatting Dates Without Libraries
Intl.DateTimeFormat is built into modern browsers and does locale-aware date formatting without any library. new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' }).format(date) gives 'March 15, 2026'. Change 'en-US' to 'de-DE' and you get '15. Mรคrz 2026'. This handles internationalization without shipping any additional JavaScript.
Debugging tip
When debugging a timezone-related bug, log date.toISOString() instead of date.toString(). The ISO string is always UTC and unambiguous. The toString() output varies by system locale and makes bugs harder to reproduce across environments.
Frequently Asked Questions
Why is JavaScript date handling so bad?+
Should I still use Moment.js?+
How do I avoid timezone bugs?+
What is the Temporal API?+
๐ง Free Tools Used in This Guide
FreeToolKit Team
FreeToolKit Team
We build free browser-based tools and write practical guides that skip the fluff.
Tags: