The Date Object
JavaScript's Date represents a moment in time as milliseconds since the Unix epoch (January 1, 1970 00:00:00 UTC).
// Creating dates
new Date() // current moment
new Date(timestamp) // from milliseconds
new Date('2026-05-26') // from ISO string (UTC midnight)
new Date('2026-05-26T14:30:00Z') // ISO with UTC time
new Date('2026-05-26T14:30:00+05:30') // ISO with timezone offset
new Date(2026, 4, 26) // year, month (0-indexed!), day — local time
new Date(2026, 4, 26, 14, 30, 0) // with hours, minutes, seconds
Gotcha: Month is 0-indexed (0=January, 11=December). Day is 1-indexed. This inconsistency trips up everyone.
Getting the Current Timestamp
Date.now() // milliseconds since epoch (most efficient)
new Date().getTime() // same thing
+new Date() // same, via coercion (avoid — less readable)
Extracting Components
const d = new Date('2026-05-26T14:30:00Z');
// UTC methods — independent of system timezone
d.getUTCFullYear() // 2026
d.getUTCMonth() // 4 (May, 0-indexed)
d.getUTCDate() // 26 (day of month)
d.getUTCHours() // 14
d.getUTCMinutes() // 30
d.getUTCSeconds() // 0
d.getUTCDay() // 1 (Monday, 0=Sunday)
// Local methods — depend on system timezone
d.getFullYear() // might be different depending on your timezone
d.getMonth() // same caveat
d.getDate()
d.getHours()
Rule: Use UTC methods unless you specifically need local-time behavior. Mixed use causes subtle bugs.
Formatting Dates
Intl.DateTimeFormat (Built-in, no library needed)
const d = new Date('2026-05-26T14:30:00Z');
// US format
new Intl.DateTimeFormat('en-US').format(d)
// => "5/26/2026"
// UK format
new Intl.DateTimeFormat('en-GB').format(d)
// => "26/05/2026"
// Full readable
new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
timeZone: 'America/New_York',
}).format(d)
// => "May 26, 2026 at 10:30 AM"
// Relative time (needs a reference point)
new Intl.RelativeTimeFormat('en').format(-3, 'days')
// => "3 days ago"
ISO 8601 String
new Date().toISOString()
// => "2026-05-26T14:30:00.000Z"
// For date-only
new Date().toISOString().split('T')[0]
// => "2026-05-26"
Manual Formatting
function formatDate(date, format = 'YYYY-MM-DD') {
const y = date.getUTCFullYear();
const m = String(date.getUTCMonth() + 1).padStart(2, '0');
const d = String(date.getUTCDate()).padStart(2, '0');
const H = String(date.getUTCHours()).padStart(2, '0');
const M = String(date.getUTCMinutes()).padStart(2, '0');
return format
.replace('YYYY', y)
.replace('MM', m)
.replace('DD', d)
.replace('HH', H)
.replace('mm', M);
}
formatDate(new Date(), 'YYYY-MM-DD HH:mm')
// => "2026-05-26 14:30"
Date Arithmetic
JavaScript dates don't have built-in arithmetic methods. Work with timestamps:
const now = new Date();
// Add days
const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
// Add months (timezone-safe)
const nextMonth = new Date(now);
nextMonth.setUTCMonth(nextMonth.getUTCMonth() + 1);
// Difference in days
function daysBetween(a, b) {
const msPerDay = 24 * 60 * 60 * 1000;
return Math.round((b.getTime() - a.getTime()) / msPerDay);
}
daysBetween(new Date('2026-01-01'), new Date('2026-05-26'))
// => 145
Comparing Dates
const a = new Date('2026-05-26');
const b = new Date('2026-06-01');
// Compare timestamps
a < b // true
a > b // false
a <= b // true
// Check equality (can't use == or === — compares object references)
a.getTime() === b.getTime() // false
a.toISOString() === b.toISOString() // false
// Is date in the past?
new Date('2026-01-01') < Date.now() // true (auto-coercion works here)
Time Zones
The hardest part of working with dates. JavaScript's Date stores UTC internally; display is affected by the system's local timezone.
const d = new Date('2026-05-26T12:00:00Z'); // UTC noon
// What time is this in different zones?
d.toLocaleTimeString('en-US', { timeZone: 'America/New_York' })
// => "8:00:00 AM" (EDT, UTC-4)
d.toLocaleTimeString('en-US', { timeZone: 'Asia/Tokyo' })
// => "9:00:00 PM" (JST, UTC+9)
d.toLocaleTimeString('en-US', { timeZone: 'Europe/Berlin' })
// => "2:00:00 PM" (CEST, UTC+2)
Converting to a Specific Timezone
// Get the UTC offset for a timezone at a specific moment
function getTimezoneOffset(timezone, date = new Date()) {
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }));
return (utcDate - tzDate) / 60000; // minutes
}
// Parse a local time string as if it's in a specific timezone
function parseDateInTimezone(dateStr, timezone) {
return new Date(
new Intl.DateTimeFormat('en-CA', {
timeZone: timezone,
year: 'numeric', month: '2-digit', day: '2-digit',
}).format(new Date(dateStr))
);
}
Best Practice: Store UTC, Display Local
// ✅ Store timestamps in UTC (ISO 8601 or Unix ms)
const event = {
title: 'Team standup',
startTime: '2026-05-26T09:00:00Z', // UTC
timezone: 'America/New_York', // creator's timezone
};
// Display in user's local timezone
function displayEventTime(event, userTimezone) {
return new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium',
timeStyle: 'short',
timeZone: userTimezone,
}).format(new Date(event.startTime));
}
Common Patterns
Start of Day / End of Day (UTC)
function startOfDay(date) {
const d = new Date(date);
d.setUTCHours(0, 0, 0, 0);
return d;
}
function endOfDay(date) {
const d = new Date(date);
d.setUTCHours(23, 59, 59, 999);
return d;
}
Is Same Day?
function isSameDay(a, b, timezone = 'UTC') {
const fmt = new Intl.DateTimeFormat('en-CA', {
timeZone: timezone,
year: 'numeric', month: '2-digit', day: '2-digit',
});
return fmt.format(a) === fmt.format(b);
}
Format "Time Ago"
function timeAgo(date) {
const seconds = Math.floor((Date.now() - new Date(date).getTime()) / 1000);
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
if (seconds < 60) return rtf.format(-seconds, 'second');
if (seconds < 3600) return rtf.format(-Math.floor(seconds / 60), 'minute');
if (seconds < 86400) return rtf.format(-Math.floor(seconds / 3600), 'hour');
if (seconds < 2592000) return rtf.format(-Math.floor(seconds / 86400), 'day');
if (seconds < 31536000) return rtf.format(-Math.floor(seconds / 2592000), 'month');
return rtf.format(-Math.floor(seconds / 31536000), 'year');
}
timeAgo(new Date(Date.now() - 3 * 60 * 1000)) // "3 minutes ago"
timeAgo(new Date(Date.now() - 2 * 3600 * 1000)) // "2 hours ago"
The Temporal API (Upcoming Standard)
The Temporal API is a new standard (Stage 3 proposal as of 2026) that fixes Date's design flaws with separate types for different concepts:
// Polyfill: npm install @js-temporal/polyfill
import { Temporal } from '@js-temporal/polyfill';
// PlainDate: date without time
const date = Temporal.PlainDate.from('2026-05-26');
date.year; // 2026
date.month; // 5 (1-indexed, unlike Date!)
date.day; // 26
// ZonedDateTime: date + time + timezone
const zdt = Temporal.ZonedDateTime.from('2026-05-26T09:00:00[America/New_York]');
zdt.toInstant().epochMilliseconds; // UTC milliseconds
// Arithmetic
const tomorrow = date.add({ days: 1 });
const nextMonth = date.add({ months: 1 });
// Comparison
Temporal.PlainDate.compare(date, tomorrow) // -1 (date < tomorrow)
Temporal provides immutable date objects, proper timezone handling, and 1-indexed months — all the things Date got wrong.
→ Convert between date formats and timestamps with the Date Time Converter.