Formatting dates in JavaScript is one of the most common tasks in web development — and one of the most error-prone. From the quirky 0-indexed months to timezone pitfalls, there are many ways to get it wrong. This comprehensive guide covers every approach to JavaScript date formatting: native methods, Intl.DateTimeFormat, custom format functions, and popular libraries like date-fns and Day.js, all with runnable code examples.
1. The Date Constructor: Creating Dates in JavaScript
JavaScript's Date object is the foundation of all date/time operations. There are several ways to create a Date instance, each with different behaviors and gotchas.
Key things to remember: new Date() with no arguments returns the current date/time. Date.now() returns the current timestamp in milliseconds. Date.parse() returns milliseconds from a date string (or NaN if invalid).
When passing a string to the Date constructor, always use ISO 8601 format (YYYY-MM-DDTHH:mm:ss.sssZ) for consistent cross-browser behavior. Other formats like "March 5, 2024" or "03/05/2024" are implementation-dependent and may behave differently across engines.
Different ways to create a Date
// 1. No arguments — current date and time
const now = new Date();
console.log(now);
// e.g. "Fri Mar 15 2024 14:30:45 GMT+0800"
// 2. Milliseconds since epoch (Jan 1, 1970 UTC)
const fromMs = new Date(1710505845000);
console.log(fromMs.toISOString());
// "2024-03-15T14:30:45.000Z"
// 3. Date.now() — current timestamp in milliseconds
const timestamp = Date.now();
console.log(timestamp);
// e.g. 1710505845000
// 4. Year, month (0-indexed!), day, hours, minutes, seconds, ms
const specific = new Date(2024, 2, 15, 14, 30, 45);
// ^^^^ ^ ^^
// year month=March (0=Jan, 2=Mar)
console.log(specific);
// 5. ISO 8601 string (recommended for parsing)
const fromISO = new Date("2024-03-15T14:30:45.000Z");
console.log(fromISO.toISOString());
// "2024-03-15T14:30:45.000Z"Date.parse() and ISO 8601 strings
// Date.parse() returns milliseconds (or NaN for invalid strings)
const ms1 = Date.parse("2024-03-15T14:30:45.000Z");
console.log(ms1); // 1710505845000
const ms2 = Date.parse("2024-03-15");
console.log(ms2); // 1710460800000 (midnight UTC)
const ms3 = Date.parse("not a date");
console.log(ms3); // NaN
// ISO 8601 variants that all work
Date.parse("2024-03-15"); // date only (UTC)
Date.parse("2024-03-15T14:30:45Z"); // with time and Z (UTC)
Date.parse("2024-03-15T14:30:45+08:00"); // with timezone offset
Date.parse("2024-03-15T14:30:45.123Z"); // with millisecondsCommon constructor pitfalls
// PITFALL 1: Month is 0-indexed
const jan = new Date(2024, 0, 1); // January 1 (NOT month 0!)
const dec = new Date(2024, 11, 31); // December 31
// PITFALL 2: Date-only strings are parsed as UTC
const utcMidnight = new Date("2024-03-15");
console.log(utcMidnight.toISOString());
// "2024-03-15T00:00:00.000Z" — UTC midnight
// But date-time strings WITHOUT Z are parsed as LOCAL
const localMidnight = new Date("2024-03-15T00:00:00");
console.log(localMidnight.toISOString());
// "2024-03-14T16:00:00.000Z" — if you are in UTC+8
// PITFALL 3: Two-digit years
const twoDigit = new Date(99, 0, 1);
console.log(twoDigit.getFullYear());
// 1999 (NOT 99 or 2099!)
// PITFALL 4: Invalid dates don't throw
const invalid = new Date("banana");
console.log(invalid); // Invalid Date
console.log(invalid.getTime()); // NaN2. Getter Methods: Extracting Date Components
Once you have a Date object, you can extract individual components using getter methods. The most important gotcha: getMonth() returns 0-11, not 1-12. January is 0, December is 11.
Every getter has a UTC counterpart (e.g., getUTCFullYear(), getUTCMonth()) that returns values in UTC instead of local time. Always use the UTC variants when working with server-side dates or timestamps.
All getter methods with output
const date = new Date("2024-03-15T14:30:45.123Z");
// Year
date.getFullYear(); // 2024 (always 4 digits)
date.getUTCFullYear(); // 2024
// Month (0-INDEXED! January = 0, December = 11)
date.getMonth(); // 2 (March, NOT 3!)
date.getUTCMonth(); // 2
// To display: date.getMonth() + 1 => 3
// Day of month (1-31)
date.getDate(); // 15
date.getUTCDate(); // 15
// Day of week (0 = Sunday, 6 = Saturday)
date.getDay(); // 5 (Friday)
date.getUTCDay(); // 5
// Hours (0-23)
date.getHours(); // depends on local timezone
date.getUTCHours(); // 14
// Minutes (0-59)
date.getMinutes(); // depends on local timezone
date.getUTCMinutes(); // 30
// Seconds (0-59)
date.getSeconds(); // 45
date.getUTCSeconds(); // 45
// Milliseconds (0-999)
date.getMilliseconds(); // 123
date.getUTCMilliseconds(); // 123
// Timestamp (milliseconds since epoch)
date.getTime(); // 1710505845123
date.valueOf(); // 1710505845123 (same as getTime)
// Timezone offset (minutes from UTC, sign is reversed!)
date.getTimezoneOffset(); // -480 for UTC+8 (negative = east of UTC)UTC vs Local getters
// If your local timezone is UTC+8 (e.g., Beijing, Singapore)
const date = new Date("2024-03-15T22:30:00Z"); // 10:30 PM UTC
// LOCAL getters (depend on the machine's timezone)
console.log(date.getDate()); // 16 (next day in UTC+8!)
console.log(date.getHours()); // 6 (6:30 AM local time)
// UTC getters (always consistent)
console.log(date.getUTCDate()); // 15
console.log(date.getUTCHours()); // 22
// Rule of thumb:
// - Use UTC getters for data processing, storage, APIs
// - Use local getters only for displaying in the user's timezone3. toLocaleDateString & Intl.DateTimeFormat: Locale-Aware Formatting
toLocaleDateString() and Intl.DateTimeFormat are the modern, built-in way to format dates for different locales without any library. They automatically handle locale-specific number systems, calendar systems, and formatting conventions.
The options object lets you control exactly which parts of the date/time are shown and how. Use dateStyle/timeStyle for quick presets, or specify individual components for full control.
toLocaleDateString() basics
const date = new Date("2024-03-15T14:30:45Z");
// Default (uses browser locale)
date.toLocaleDateString();
// "3/15/2024" (en-US) or "2024/3/15" (zh-CN)
// Specify locale
date.toLocaleDateString('en-US'); // "3/15/2024"
date.toLocaleDateString('en-GB'); // "15/03/2024"
date.toLocaleDateString('de-DE'); // "15.3.2024"
date.toLocaleDateString('ja-JP'); // "2024/3/15"
date.toLocaleDateString('zh-CN'); // "2024/3/15"
date.toLocaleDateString('ko-KR'); // "2024. 3. 15."
// With options
date.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
// "Friday, March 15, 2024"
// toLocaleTimeString for time only
date.toLocaleTimeString('en-US');
// "2:30:45 PM" (or adjusted for timezone)
// toLocaleString for both date and time
date.toLocaleString('en-US');
// "3/15/2024, 2:30:45 PM"Intl.DateTimeFormat with options
const date = new Date("2024-03-15T14:30:45Z");
// Quick presets with dateStyle / timeStyle
new Intl.DateTimeFormat('en-US', {
dateStyle: 'full',
timeStyle: 'long',
timeZone: 'America/New_York'
}).format(date);
// "Friday, March 15, 2024 at 10:30:45 AM EDT"
// Individual component control
new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'UTC'
}).format(date);
// "03/15/2024, 14:30:45"
// Chinese format with era
new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
timeZone: 'Asia/Shanghai'
}).format(date);
// "2024年3月15日星期五"
// Month names
new Intl.DateTimeFormat('en-US', { month: 'long' }).format(date);
// "March"
new Intl.DateTimeFormat('en-US', { month: 'short' }).format(date);
// "Mar"
new Intl.DateTimeFormat('en-US', { month: 'narrow' }).format(date);
// "M"formatToParts() for custom assembly
const date = new Date("2024-03-15T14:30:45Z");
// formatToParts returns an array of { type, value } objects
const formatter = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
timeZone: 'UTC'
});
const parts = formatter.formatToParts(date);
console.log(parts);
// [
// { type: 'month', value: 'March' },
// { type: 'literal', value: ' ' },
// { type: 'day', value: '15' },
// { type: 'literal', value: ', ' },
// { type: 'year', value: '2024' },
// { type: 'literal', value: ', ' },
// { type: 'hour', value: '2' },
// { type: 'literal', value: ':' },
// { type: 'minute', value: '30' },
// { type: 'literal', value: ' ' },
// { type: 'dayPeriod', value: 'PM' }
// ]
// Extract specific parts
const month = parts.find(p => p.type === 'month')?.value; // "March"
const year = parts.find(p => p.type === 'year')?.value; // "2024"
// Build custom format: "March 2024"
const custom = `${month} ${year}`;4. ISO 8601 Format: toISOString() and toJSON()
ISO 8601 is the international standard for date/time representation. It is unambiguous, sortable, and universally parseable. JavaScript's toISOString() outputs in the format YYYY-MM-DDTHH:mm:ss.sssZ, always in UTC.
toJSON() calls toISOString() internally, making it the default format when serializing dates with JSON.stringify(). This is the safest format for APIs and data exchange.
ISO 8601 methods
const date = new Date("2024-03-15T14:30:45.123Z");
// toISOString() — always UTC, always 24 characters
date.toISOString();
// "2024-03-15T14:30:45.123Z"
// toJSON() — same output, used by JSON.stringify()
date.toJSON();
// "2024-03-15T14:30:45.123Z"
// Proof that JSON.stringify uses toJSON:
const obj = { created: date, name: "test" };
JSON.stringify(obj);
// '{"created":"2024-03-15T14:30:45.123Z","name":"test"}'
// Extract date only from ISO string
date.toISOString().slice(0, 10);
// "2024-03-15"
// Extract time only
date.toISOString().slice(11, 19);
// "14:30:45"
// Other built-in string methods
date.toUTCString();
// "Fri, 15 Mar 2024 14:30:45 GMT"
date.toDateString();
// "Fri Mar 15 2024" (local)
date.toTimeString();
// "22:30:45 GMT+0800 (China Standard Time)" (local)UTC vs Local: the critical difference
// The same moment in time, displayed differently:
const date = new Date("2024-03-15T23:30:00Z"); // 11:30 PM UTC
// UTC representations (always March 15)
date.toISOString(); // "2024-03-15T23:30:00.000Z"
date.toUTCString(); // "Fri, 15 Mar 2024 23:30:00 GMT"
// Local representations (if you are in UTC+8)
date.toString();
// "Sat Mar 16 2024 07:30:00 GMT+0800 (China Standard Time)"
// Notice: it shows March 16! Because 23:30 UTC = 07:30 next day in UTC+8
date.toLocaleDateString('en-US');
// "3/16/2024" — different date!
// This is why you should ALWAYS use UTC for storage/APIs
// and only convert to local for display5. Custom Format Function: Building "YYYY-MM-DD HH:mm:ss" Manually
Sometimes you need a specific format string that built-in methods cannot produce. Building your own format function using padStart() is straightforward and avoids adding a library dependency.
The key technique is using String.prototype.padStart(2, '0') to ensure single-digit months, days, hours, minutes, and seconds are zero-padded.
Basic custom format function
// Simple "YYYY-MM-DD HH:mm:ss" formatter
function formatDate(date) {
const pad = (n) => String(n).padStart(2, '0');
const year = date.getFullYear();
const month = pad(date.getMonth() + 1); // +1 because 0-indexed
const day = pad(date.getDate());
const hours = pad(date.getHours());
const mins = pad(date.getMinutes());
const secs = pad(date.getSeconds());
return `${year}-${month}-${day} ${hours}:${mins}:${secs}`;
}
console.log(formatDate(new Date()));
// "2024-03-15 14:30:45"
// UTC version
function formatDateUTC(date) {
const pad = (n) => String(n).padStart(2, '0');
return [
date.getUTCFullYear(),
pad(date.getUTCMonth() + 1),
pad(date.getUTCDate())
].join('-') + ' ' + [
pad(date.getUTCHours()),
pad(date.getUTCMinutes()),
pad(date.getUTCSeconds())
].join(':');
}
console.log(formatDateUTC(new Date()));
// "2024-03-15 06:30:45" (UTC)Advanced: token-based formatter
// Token-based formatter (like date-fns / moment)
function formatDateTokens(date, format) {
const pad = (n, len = 2) => String(n).padStart(len, '0');
const tokens = {
'YYYY': () => date.getFullYear(),
'YY': () => String(date.getFullYear()).slice(-2),
'MM': () => pad(date.getMonth() + 1),
'M': () => date.getMonth() + 1,
'DD': () => pad(date.getDate()),
'D': () => date.getDate(),
'HH': () => pad(date.getHours()),
'H': () => date.getHours(),
'hh': () => pad(date.getHours() % 12 || 12),
'h': () => date.getHours() % 12 || 12,
'mm': () => pad(date.getMinutes()),
'ss': () => pad(date.getSeconds()),
'SSS': () => pad(date.getMilliseconds(), 3),
'A': () => date.getHours() < 12 ? 'AM' : 'PM',
'a': () => date.getHours() < 12 ? 'am' : 'pm',
};
// Replace longest tokens first to avoid partial matches
let result = format;
for (const [token, fn] of Object.entries(tokens).sort(
(a, b) => b[0].length - a[0].length
)) {
result = result.replace(new RegExp(token, 'g'), String(fn()));
}
return result;
}
// Usage
const now = new Date("2024-03-15T14:30:45.123Z");
formatDateTokens(now, 'YYYY-MM-DD'); // "2024-03-15"
formatDateTokens(now, 'MM/DD/YYYY'); // "03/15/2024"
formatDateTokens(now, 'HH:mm:ss'); // "14:30:45"
formatDateTokens(now, 'hh:mm A'); // "02:30 PM"
formatDateTokens(now, 'YYYY-MM-DD HH:mm:ss.SSS'); // "2024-03-15 14:30:45.123"6. date-fns: Modern, Modular Date Formatting
date-fns is a modern JavaScript date utility library that is modular (tree-shakeable), immutable, and works with native Date objects. It is the most popular alternative to Moment.js.
Install with npm install date-fns. Each function can be imported individually, so your bundle only includes what you use.
format() — the main formatting function
import { format } from 'date-fns';
const date = new Date("2024-03-15T14:30:45Z");
// Basic format patterns (date-fns uses different tokens than moment!)
format(date, 'yyyy-MM-dd'); // "2024-03-15"
format(date, 'dd/MM/yyyy'); // "15/03/2024"
format(date, 'MMMM d, yyyy'); // "March 15, 2024"
format(date, 'EEE, MMM d, yyyy'); // "Fri, Mar 15, 2024"
format(date, 'yyyy-MM-dd HH:mm:ss'); // "2024-03-15 14:30:45"
format(date, 'hh:mm a'); // "02:30 PM"
format(date, "yyyy-MM-dd'T'HH:mm:ssXXX"); // "2024-03-15T14:30:45+00:00"
// date-fns token reference:
// yyyy = 4-digit year yy = 2-digit year
// MM = 2-digit month M = 1-2 digit month
// MMMM = full month name MMM = abbreviated month
// dd = 2-digit day d = 1-2 digit day
// HH = 24-hour hh = 12-hour
// mm = minutes ss = seconds
// a = am/pm EEEE = full weekday name
// With locale
import { ja } from 'date-fns/locale';
format(date, 'PPP', { locale: ja });
// "2024年3月15日"parse() — parsing custom strings
import { parse, isValid } from 'date-fns';
// Parse a custom-formatted string into a Date
const date1 = parse('15/03/2024', 'dd/MM/yyyy', new Date());
console.log(date1.toISOString());
// "2024-03-15T00:00:00.000Z"
const date2 = parse('March 15, 2024 2:30 PM', 'MMMM d, yyyy h:mm a', new Date());
console.log(date2.toISOString());
// "2024-03-15T14:30:00.000Z"
// Validate parsed date
const result = parse('31/02/2024', 'dd/MM/yyyy', new Date());
console.log(isValid(result)); // false (Feb 31 doesn't exist)
// parseISO for ISO strings (faster than parse)
import { parseISO } from 'date-fns';
const date3 = parseISO('2024-03-15T14:30:45Z');
console.log(isValid(date3)); // truedifferenceInDays() and other calculations
import {
differenceInDays,
differenceInHours,
differenceInYears,
addDays,
subMonths,
isAfter,
isBefore,
formatDistanceToNow
} from 'date-fns';
const date1 = new Date('2024-03-15');
const date2 = new Date('2024-01-01');
// Difference calculations
differenceInDays(date1, date2); // 74
differenceInHours(date1, date2); // 1776
differenceInYears(date1, date2); // 0
// Date arithmetic
addDays(date1, 30); // April 14, 2024
subMonths(date1, 2); // January 15, 2024
// Comparisons
isAfter(date1, date2); // true
isBefore(date1, date2); // false
// Relative time (human-readable)
formatDistanceToNow(new Date('2024-01-01'));
// "3 months ago" (varies by current date)
// formatDistance between two dates
import { formatDistance } from 'date-fns';
formatDistance(date2, date1);
// "3 months"7. Day.js: Lightweight Moment.js Alternative
Day.js is a 2KB immutable date library with a Moment.js-compatible API. It is ideal when you need a chainable API with a tiny footprint.
Install with npm install dayjs. Day.js uses a plugin system for extended functionality like relative time, custom formats, and timezone support.
Basic formatting
import dayjs from 'dayjs';
const date = dayjs('2024-03-15T14:30:45Z');
// Format (uses Moment.js-compatible tokens)
date.format('YYYY-MM-DD'); // "2024-03-15"
date.format('DD/MM/YYYY'); // "15/03/2024"
date.format('MMMM D, YYYY'); // "March 15, 2024"
date.format('ddd, MMM D, YYYY'); // "Fri, Mar 15, 2024"
date.format('YYYY-MM-DD HH:mm:ss'); // "2024-03-15 14:30:45"
date.format('hh:mm A'); // "02:30 PM"
// Day.js token reference:
// YYYY = 4-digit year YY = 2-digit year
// MM = 2-digit month M = 1-2 digit month
// MMMM = full month MMM = abbreviated month
// DD = 2-digit day D = 1-2 digit day
// HH = 24-hour hh = 12-hour
// mm = minutes ss = seconds
// A = AM/PM dddd = full weekday
// Getters
date.year(); // 2024
date.month(); // 2 (0-indexed, like native Date)
date.date(); // 15
date.day(); // 5 (Friday)
date.hour(); // 14
date.minute(); // 30
date.second(); // 45Relative time with fromNow()
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
// fromNow() — relative to current time
dayjs('2024-01-01').fromNow(); // "3 months ago"
dayjs('2025-01-01').fromNow(); // "in 10 months"
dayjs().subtract(2, 'hour').fromNow(); // "2 hours ago"
// from() — relative to another date
const start = dayjs('2024-01-01');
const end = dayjs('2024-03-15');
end.from(start); // "3 months ago"
start.to(end); // "in 3 months"
// toNow() — inverse of fromNow
dayjs('2024-01-01').toNow(); // "in 3 months"
// Without suffix
dayjs('2024-01-01').fromNow(true); // "3 months"Plugin system
import dayjs from 'dayjs';
// UTC plugin
import utc from 'dayjs/plugin/utc';
dayjs.extend(utc);
dayjs.utc('2024-03-15T14:30:45Z').format();
// "2024-03-15T14:30:45Z"
// Timezone plugin (requires utc plugin first)
import timezone from 'dayjs/plugin/timezone';
dayjs.extend(timezone);
dayjs('2024-03-15T14:30:45Z').tz('Asia/Tokyo').format('YYYY-MM-DD HH:mm:ss');
// "2024-03-15 23:30:45"
// CustomParseFormat plugin
import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat);
dayjs('15-03-2024', 'DD-MM-YYYY').format('YYYY-MM-DD');
// "2024-03-15"
// Duration plugin
import duration from 'dayjs/plugin/duration';
dayjs.extend(duration);
const dur = dayjs.duration(3661000); // milliseconds
dur.hours(); // 1
dur.minutes(); // 1
dur.seconds(); // 1
dur.humanize(); // "an hour"
// Locale
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
dayjs('2024-03-15').format('MMMM D, YYYY dddd');
// "三月 15, 2024 星期五"8. Common Date Format Patterns Reference
Here is a quick reference table showing the most commonly used date format patterns across JavaScript and popular libraries.
| Pattern | Example Output | Method / Library | Notes |
|---|---|---|---|
YYYY-MM-DD | 2024-03-15 | toISOString().slice(0,10) | ISO standard, sortable, API-friendly |
MM/DD/YYYY | 03/15/2024 | Custom function | US common format |
DD/MM/YYYY | 15/03/2024 | Custom function | Europe/Asia common format |
DD MMM YYYY | 15 Mar 2024 | Intl.DateTimeFormat | Human-readable short format |
MMMM D, YYYY | March 15, 2024 | Intl.DateTimeFormat | Formal long format |
HH:mm:ss | 14:30:45 | Custom function | 24-hour time |
hh:mm A | 02:30 PM | Intl.DateTimeFormat | 12-hour with AM/PM |
YYYY-MM-DDTHH:mm:ssZ | 2024-03-15T14:30:45Z | toISOString() | Full ISO 8601 with timezone |
Relative | 3 days ago | Day.js / date-fns | Requires library |
Unix Timestamp | 1710505845 | Date.now() / 1000 | Seconds since epoch |
// Quick reference: achieving each format in vanilla JS
const d = new Date("2024-03-15T14:30:45Z");
const pad = n => String(n).padStart(2, '0');
// YYYY-MM-DD
d.toISOString().slice(0, 10); // "2024-03-15"
// MM/DD/YYYY
`${pad(d.getUTCMonth()+1)}/${pad(d.getUTCDate())}/${d.getUTCFullYear()}`;
// "03/15/2024"
// DD MMM YYYY
new Intl.DateTimeFormat('en-GB', {
day: '2-digit', month: 'short', year: 'numeric', timeZone: 'UTC'
}).format(d); // "15 Mar 2024"
// Full date with weekday
new Intl.DateTimeFormat('en-US', {
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
timeZone: 'UTC'
}).format(d); // "Friday, March 15, 2024"
// 12-hour time
new Intl.DateTimeFormat('en-US', {
hour: 'numeric', minute: '2-digit', hour12: true, timeZone: 'UTC'
}).format(d); // "2:30 PM"9. Timezone Handling: Intl.DateTimeFormat with timeZone
Timezone handling is one of the trickiest aspects of JavaScript date formatting. The Date object internally stores time in UTC, but most display methods convert to the user's local timezone.
Intl.DateTimeFormat with the timeZone option is the most reliable way to format dates in a specific timezone without external libraries.
Common pitfalls: new Date("2024-03-15") is parsed as UTC midnight, but new Date("2024-03-15T00:00:00") is parsed as local midnight. This difference can shift the displayed date by a whole day depending on the user's timezone.
Formatting in specific timezones
const date = new Date("2024-03-15T14:30:45Z");
// Format in different timezones
const timezones = [
'UTC',
'America/New_York',
'Europe/London',
'Asia/Shanghai',
'Asia/Tokyo',
'Australia/Sydney'
];
const fmt = (tz) => new Intl.DateTimeFormat('en-US', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false, timeZone: tz, timeZoneName: 'short'
}).format(date);
timezones.forEach(tz => console.log(`${tz.padEnd(22)} ${fmt(tz)}`));
// UTC 03/15/2024, 14:30:45 UTC
// America/New_York 03/15/2024, 10:30:45 EDT
// Europe/London 03/15/2024, 14:30:45 GMT
// Asia/Shanghai 03/15/2024, 22:30:45 GMT+8
// Asia/Tokyo 03/15/2024, 23:30:45 JST
// Australia/Sydney 03/16/2024, 01:30:45 AEDTUTC conversion pitfalls
// TRAP 1: Date-only string vs date-time string
const dateOnly = new Date("2024-03-15"); // Parsed as UTC midnight
const dateTime = new Date("2024-03-15T00:00:00"); // Parsed as LOCAL midnight
// In UTC+8, these are 8 hours apart!
console.log(dateOnly.toISOString());
// "2024-03-15T00:00:00.000Z"
console.log(dateTime.toISOString());
// "2024-03-14T16:00:00.000Z" <-- previous day in UTC!
// TRAP 2: getDate() can return a different day
const lateUTC = new Date("2024-03-15T23:00:00Z");
// In UTC+8:
console.log(lateUTC.getDate()); // 16 (next day locally!)
console.log(lateUTC.getUTCDate()); // 15 (correct UTC date)
// SOLUTION: Always be explicit about timezone
// Use toISOString() for UTC, Intl with timeZone for specific zones
const safeFormat = new Intl.DateTimeFormat('en-CA', {
year: 'numeric', month: '2-digit', day: '2-digit',
timeZone: 'UTC'
}).format(lateUTC);
// "2024-03-15" (correct, because we specified UTC)Getting the timezone offset
const date = new Date();
// getTimezoneOffset() returns minutes FROM local TO UTC
// CAREFUL: the sign is reversed from what you might expect!
const offsetMinutes = date.getTimezoneOffset();
// -480 for UTC+8 (negative means east of UTC)
// 300 for UTC-5 (positive means west of UTC)
// Convert to hours
const offsetHours = -offsetMinutes / 60;
// 8 for UTC+8
// -5 for UTC-5
// Format as "+HH:MM" / "-HH:MM"
function getTimezoneString(date) {
const offset = -date.getTimezoneOffset();
const sign = offset >= 0 ? '+' : '-';
const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0');
const minutes = String(Math.abs(offset) % 60).padStart(2, '0');
return `${sign}${hours}:${minutes}`;
}
console.log(getTimezoneString(new Date()));
// "+08:00" (for UTC+8)
// Get IANA timezone name
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log(tz);
// "Asia/Shanghai" (varies by system)10. Date Validation: Checking for Invalid Dates
JavaScript's Date constructor never throws an error — it returns an "Invalid Date" object instead. You need to explicitly check for this using isNaN() or Number.isNaN().
Edge cases abound: new Date("2024-02-30") silently rolls over to March 1st. new Date("") returns Invalid Date, but new Date(0) returns January 1, 1970.
Checking for Invalid Date
// Method 1: isNaN() — most common
function isValidDate(date) {
return date instanceof Date && !isNaN(date);
}
// Method 2: Number.isNaN() — stricter
function isValidDate2(date) {
return date instanceof Date && !Number.isNaN(date.getTime());
}
// Method 3: getTime() check
function isValidDate3(date) {
return date instanceof Date && isFinite(date.getTime());
}
// Test cases
isValidDate(new Date("2024-03-15")); // true
isValidDate(new Date("not a date")); // false
isValidDate(new Date("")); // false
isValidDate(new Date(NaN)); // false
isValidDate(new Date(0)); // true (Jan 1, 1970)
isValidDate(new Date(Infinity)); // falseEdge cases and silent rollovers
// JavaScript silently "fixes" out-of-range values by rolling over
// Feb 30 -> rolls to March 1
const feb30 = new Date(2024, 1, 30); // month 1 = February
console.log(feb30.toISOString().slice(0, 10));
// "2024-03-01" (silently rolled over!)
// Month 13 -> rolls to next year
const month13 = new Date(2024, 12, 1); // month 12 = January next year
console.log(month13.toISOString().slice(0, 10));
// "2025-01-01"
// Day 0 -> last day of previous month
const day0 = new Date(2024, 2, 0); // March 0 = Feb 29 (leap year!)
console.log(day0.toISOString().slice(0, 10));
// "2024-02-29"
// Negative day -> further back
const dayNeg = new Date(2024, 2, -1); // March -1 = Feb 28
console.log(dayNeg.toISOString().slice(0, 10));
// "2024-02-28"Robust validation function
// Robust validation that catches silent rollovers
function isValidDateStrict(year, month, day) {
// month is 1-based here (1=Jan, 12=Dec)
const date = new Date(year, month - 1, day);
return (
date.getFullYear() === year &&
date.getMonth() === month - 1 &&
date.getDate() === day
);
}
// Test cases
isValidDateStrict(2024, 3, 15); // true — March 15 exists
isValidDateStrict(2024, 2, 29); // true — 2024 is a leap year
isValidDateStrict(2023, 2, 29); // false — 2023 is NOT a leap year
isValidDateStrict(2024, 2, 30); // false — Feb 30 never exists
isValidDateStrict(2024, 13, 1); // false — month 13 doesn't exist
// Validate an ISO string
function isValidISODate(str) {
if (typeof str !== 'string') return false;
const date = new Date(str);
return !isNaN(date.getTime()) && date.toISOString().startsWith(str.slice(0, 10));
}
isValidISODate("2024-03-15"); // true
isValidISODate("2024-02-30"); // false (rolls to Mar 1)
isValidISODate("2024-13-01"); // false
isValidISODate("not-a-date"); // false11. Performance Tips: Optimizing Date Operations
Date operations can become a bottleneck when processing large datasets or rendering many date values. Here are practical tips to keep your date formatting fast.
The most impactful optimization is caching Intl.DateTimeFormat instances. Creating a new formatter is expensive; reusing one is nearly free.
Cache Intl.DateTimeFormat instances
// BAD: creating a new Intl.DateTimeFormat every time
function formatDateSlow(date) {
return new Intl.DateTimeFormat('en-US', {
year: 'numeric', month: 'short', day: 'numeric'
}).format(date);
}
// GOOD: cache the formatter instance
const cachedFormatter = new Intl.DateTimeFormat('en-US', {
year: 'numeric', month: 'short', day: 'numeric'
});
function formatDateFast(date) {
return cachedFormatter.format(date);
}
// Even better: cache per locale + options
const formatterCache = new Map();
function getFormatter(locale, options) {
const key = locale + JSON.stringify(options);
if (!formatterCache.has(key)) {
formatterCache.set(key, new Intl.DateTimeFormat(locale, options));
}
return formatterCache.get(key);
}
function formatDateCached(date, locale = 'en-US', options = {}) {
return getFormatter(locale, options).format(date);
}
// Benchmark: ~100x faster for repeated callsAvoid new Date() in tight loops
// BAD: creating Date objects in a loop
const timestamps = [1710505845, 1710592245, 1710678645 /* ... thousands */];
// Slow: new Date + new Intl.DateTimeFormat on every iteration
const slow = timestamps.map(ts => {
return new Intl.DateTimeFormat('en-US').format(new Date(ts * 1000));
});
// FAST: cache the formatter, reuse Date objects
const fmt = new Intl.DateTimeFormat('en-US', {
year: 'numeric', month: 'short', day: 'numeric'
});
const fast = timestamps.map(ts => fmt.format(new Date(ts * 1000)));
// Up to 50x faster for large arrays
// Even faster for simple formats: avoid Date entirely
function timestampToYMD(ts) {
// Quick math for UTC date (no Date object needed)
const days = Math.floor(ts / 86400);
// ... but this gets complex. Use Date for correctness.
return new Date(ts * 1000).toISOString().slice(0, 10);
}More performance tips
// TIP 1: Use Date.now() instead of new Date().getTime()
const now1 = new Date().getTime(); // Slower (creates object)
const now2 = Date.now(); // Faster (no object creation)
// TIP 2: Compare timestamps, not Date objects
const d1 = new Date('2024-03-15');
const d2 = new Date('2024-03-16');
// Slow: string comparison
d1.toISOString() < d2.toISOString(); // true, but slow
// Fast: number comparison
d1.getTime() < d2.getTime(); // true, fast
// TIP 3: Memoize formatted results if the same dates are displayed repeatedly
const formatMemo = new Map();
function formatWithMemo(date, locale = 'en-US') {
const key = date.getTime() + locale;
if (!formatMemo.has(key)) {
formatMemo.set(key, getFormatter(locale, {
dateStyle: 'medium'
}).format(date));
}
return formatMemo.get(key);
}
// TIP 4: For server-side rendering, consider pre-formatting dates
// to avoid hydration mismatches between server and client timezonesFrequently Asked Questions
How do I format a date as "YYYY-MM-DD" in JavaScript?
The simplest way is to use toISOString().slice(0, 10), which returns the date portion of the ISO 8601 string. For more control, build a custom function: const pad = n => String(n).padStart(2, "0"); const formatted = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`. Alternatively, use date-fns format(date, "yyyy-MM-dd") or dayjs(date).format("YYYY-MM-DD").
Why does getMonth() return 0 for January?
JavaScript inherited its Date API from Java 1.0 (1995), which used 0-indexed months. This was a design decision to make months work as array indices. While Java later deprecated this in favor of java.time, JavaScript kept the original behavior. Always remember to add 1 when displaying months: getMonth() + 1.
What is the best JavaScript date library in 2024?
For most projects, the built-in Intl.DateTimeFormat is sufficient and requires no dependencies. If you need a library: date-fns is best for tree-shaking and functional style (import only what you need); Day.js is best for a tiny bundle with Moment.js-compatible API; Luxon is best for complex timezone operations. Moment.js is in maintenance mode and should not be used in new projects.
How do I convert a date string to a Date object safely?
Always use ISO 8601 format for parsing: new Date("2024-03-15T10:30:00Z"). For other formats, use date-fns parse() or Day.js with the customParseFormat plugin. Never rely on new Date("03/15/2024") as it is locale-dependent. After parsing, always validate: if (isNaN(date.getTime())) throw new Error("Invalid date").
How do I display a date in a specific timezone?
Use Intl.DateTimeFormat with the timeZone option: new Intl.DateTimeFormat("en-US", { timeZone: "Asia/Tokyo", dateStyle: "full", timeStyle: "long" }).format(date). This works in all modern browsers without external libraries. For more complex timezone math, consider the Temporal API (Stage 3 proposal) or the Luxon library.