DailyTools
All articles
Developer ToolsMay 13, 20269 min read

Working with Dates, Times, and Timestamps in JavaScript

Date handling in JavaScript is notoriously confusing. A practical guide to the Date object, Unix timestamps, time zones, formatting, and the libraries that make date math reliable.

Date and time handling is one of the most consistently painful areas in programming, and JavaScript's Date object has been the subject of developer complaints since its creation in 1995 (Brendan Eich reportedly copied it from Java's equally problematic java.util.Date in 10 days). Months are zero-indexed (January is 0), time zone handling is inconsistent across browsers, parsing date strings is unreliable, and there is no built-in support for date arithmetic. Despite these issues, every web application eventually needs to work with dates — and understanding the fundamentals prevents the subtle, hard-to-reproduce bugs that date code is famous for.

Unix Timestamps: The Universal Time Format

A Unix timestamp (also called epoch time) is the number of seconds that have elapsed since January 1, 1970, 00:00:00 UTC — a moment known as the Unix epoch. This simple integer representation has become the standard for storing and transmitting time values across systems because it is unambiguous (no time zone interpretation needed), compact (a single number), and universally supported.

JavaScript's Date.now() returns the current Unix timestamp in milliseconds (not seconds). Most other languages and systems (Unix command line, Python's time.time(), database TIMESTAMP types) use seconds. This difference is the most common source of timestamp bugs in JavaScript: accidentally passing a millisecond value where seconds are expected produces dates in the year 53,000+, and vice versa produces dates in January 1970.

javascript
// Current timestamp
Date.now();           // 1779955200000 (milliseconds)
Math.floor(Date.now() / 1000); // 1779955200 (seconds — Unix standard)

// Convert timestamp to Date
new Date(1779955200000);   // Correct — milliseconds
new Date(1779955200 * 1000); // Also correct — seconds converted to ms

// Common bug: passing seconds directly
new Date(1779955200);
// → "Wed Jan 21 1970 ..." — Wrong! Interpreted as milliseconds.

// Convert Date to timestamp
const date = new Date('2026-05-25T12:00:00Z');
date.getTime();     // 1779955200000 (milliseconds)
date.valueOf();     // Same as getTime()

The JavaScript Date Object

The Date object wraps a single number — the millisecond timestamp — and provides methods to extract components (year, month, day, hour, etc.) in both local time and UTC. The most confusing aspect is that months are zero-indexed: January is 0, December is 11.

javascript
// Creating dates
new Date();                          // Current date/time
new Date('2026-05-25');              // ISO 8601 string (UTC)
new Date('2026-05-25T14:30:00Z');    // ISO 8601 with time (UTC)
new Date(2026, 4, 25);              // Year, month (0-indexed!), day
//              ^ May is 4, not 5!

// Extracting components (local time)
const d = new Date('2026-05-25T14:30:00Z');
d.getFullYear();    // 2026
d.getMonth();       // 4 (May — zero-indexed)
d.getDate();        // 25
d.getDay();         // 1 (Monday — 0=Sunday, 6=Saturday)
d.getHours();       // Depends on your time zone!

// UTC equivalents
d.getUTCHours();    // 14 (always UTC regardless of local time zone)

// ISO string — the only reliable serialization format
d.toISOString();    // "2026-05-25T14:30:00.000Z"

Time Zone Pitfalls

The Date object internally stores time in UTC but displays it in the user's local time zone. This dual nature causes confusion: the same Date object shows different hours depending on where the code runs. When you create a date from an ISO string without a time zone suffix, browsers interpret it differently — some as UTC, some as local time.

  • Always store and transmit dates in UTC (ISO 8601 format with the Z suffix or explicit offset)
  • Only convert to local time for display in the UI
  • Use getUTC*() methods when comparing or computing with dates on the server
  • The string '2026-05-25' (date only, no time) is interpreted as UTC midnight by Date.parse() but local midnight by some browsers — always include the time component
  • new Date('2026-05-25T00:00:00Z') is unambiguous; new Date('2026-05-25') is not

Date Formatting with Intl.DateTimeFormat

JavaScript has no built-in date formatting function like Python's strftime(). The modern solution is the Intl.DateTimeFormat API, which formats dates according to locale-specific conventions:

javascript
const date = new Date('2026-05-25T14:30:00Z');

// Locale-aware formatting
new Intl.DateTimeFormat('en-US').format(date);       // "5/25/2026"
new Intl.DateTimeFormat('en-GB').format(date);       // "25/05/2026"
new Intl.DateTimeFormat('de-DE').format(date);       // "25.5.2026"

// Customized formatting
new Intl.DateTimeFormat('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit',
  timeZoneName: 'short'
}).format(date);
// → "May 25, 2026, 02:30 PM EDT"

// Relative time formatting
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
rtf.format(-1, 'day');     // "yesterday"
rtf.format(3, 'hour');     // "in 3 hours"
rtf.format(-2, 'week');    // "2 weeks ago"

Date Arithmetic

Adding or subtracting time from dates is surprisingly error-prone. Adding 1 month to January 31 gives February 31, which JavaScript silently rolls over to March 3 (or March 2 in leap years). Adding 24 hours is not the same as adding 1 day during daylight saving time transitions.

javascript
// Adding days — safe with timestamp math
function addDays(date, days) {
  return new Date(date.getTime() + days * 86400000);
}

// Adding months — must handle month-end overflow
function addMonths(date, months) {
  const result = new Date(date);
  const day = result.getDate();
  result.setMonth(result.getMonth() + months);
  // If the day changed, we overflowed (e.g., Jan 31 + 1 month = Mar 3)
  if (result.getDate() !== day) {
    result.setDate(0); // Set to last day of previous month
  }
  return result;
}

// Difference in days
function daysBetween(a, b) {
  const ms = Math.abs(b.getTime() - a.getTime());
  return Math.floor(ms / 86400000);
}

The Temporal API: The Future of Dates in JavaScript

The Temporal API is a Stage 3 TC39 proposal (available behind flags in some browsers and via polyfill) that completely replaces the Date object with a modern, immutable, time-zone-aware API. Temporal introduces distinct types for dates without time (Temporal.PlainDate), times without dates (Temporal.PlainTime), date-times with time zones (Temporal.ZonedDateTime), and durations (Temporal.Duration).

Until Temporal reaches full browser support, libraries like date-fns (tree-shakeable utility functions) and Luxon (immutable DateTime objects with time zone support) provide the most reliable date handling for production applications. Moment.js, while still widely used, is in maintenance mode and not recommended for new projects.

Quick Reference

  • Always use ISO 8601 format (YYYY-MM-DDTHH:mm:ssZ) for storing and transmitting dates
  • JavaScript timestamps are milliseconds; Unix timestamps are seconds — divide or multiply by 1000
  • Months are 0-indexed in JavaScript: January = 0, December = 11
  • Use Intl.DateTimeFormat for locale-aware display formatting
  • Use date-fns or Luxon for complex date arithmetic — the native Date object handles edge cases poorly
  • Store dates in UTC; convert to local time only at the display layer
  • Watch for Temporal API browser support — it will obsolete most date libraries

Try the free tool referenced in this article

Timestamp Converter