💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:
.html file with <script> tags and open it in your browserMaster JavaScript date handling, timezone conversions, and the powerful Intl API for production-grade applications
Working with dates and timezones in JavaScript is one of the most confusing parts of the language. Developers regularly run into bugs like "one day off," "wrong timezone," "midnight shifting backwards," and "timestamps showing the wrong hour." This lesson covers the Date object internals, UTC vs local time, DST behaviour, global timezones, and the Intl API for production-grade time logic.
A JavaScript Date doesn't store a timezone. It stores a single number: the milliseconds since Jan 1, 1970 UTC. Everything else — local time, UTC time, formatted strings — is calculated from that number.
Understanding what Date objects really store
const d = new Date();
console.log(d.getTime()); // milliseconds since epoch
// This single fact explains nearly all confusing behaviour:
// - new Date("2024-01-01") may shift depending on timezone
// - Formatting differs per user locale
// - Midnight conversions break around DST transitionsBest practice: always create dates with a timezone in mind.
Different methods to create dates with timezones
// Method 1: Using Date.UTC() - explicit UTC
const d1 = new Date(Date.UTC(2025, 0, 1, 12, 0));
console.log(d1.toISOString());
// Method 2: Using ISO with "Z" for UTC
const d2 = new Date("2025-01-01T12:00:00Z");
console.log(d2.toISOString());
// Method 3: Local time (dangerous but sometimes needed)
const d3 = new Date(2025, 0, 1, 12, 0);
console.log(d3.toString());❌ BAD: Ambiguous inputnew Date("2025-01-01") may shift backward or forward depending on the engine, region, and browser.
Every timezone has a base offset (e.g. UTC+1), daylight savings rules, historical changes, and political changes.
Working with different timezones in JavaScript
// Check your system timezone
const myTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log("Your timezone:", myTimezone);
// Convert a UTC date to specific timezones
const d = new Date("2025-03-11T12:00:00Z");
console.log("London:", d.toLocaleString("en-GB", { timeZone: "Europe/London" }));
console.log("New York:", d.toLocaleString("en-US", { timeZone: "America/New_York" }));
console.log("Tokyo:", d.toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" }));
console.log("Sydney:"
...Timezones often jump forward or backward one hour. In the UK, clocks go forward in March and back in October.
How DST affects date calculations
// ❌ WRONG: Adding 24 hours may not land on the next calendar day!
const d = new Date("2025-03-30T00:00:00");
d.setHours(d.getHours() + 24);
console.log("After adding 24 hours:", d);
// ✔ CORRECT: Add days with setDate()
const d2 = new Date("2025-03-30T00:00:00");
d2.setDate(d2.getDate() + 1);
console.log("After adding 1 day:", d2);
// JavaScript automatically adjusts the month/day correctlyThe correct way to compare dates in JavaScript
const date1 = new Date("2025-01-01");
const date2 = new Date("2025-01-01");
// ❌ WRONG: Comparing object references
if (date1 === date2) {
console.log("same");
} else {
console.log("Not same (objects are different references)");
}
// ✔ CORRECT: Compare timestamps
if (date1.getTime() === date2.getTime()) {
console.log("Timestamps are equal!");
}The modern way to format dates. Handles locales, languages, 24h vs 12h, timezones, and more.
Modern date formatting with internationalization
const d = new Date();
// Basic formatting
console.log("US format:", new Intl.DateTimeFormat("en-US").format(d));
console.log("UK format:", new Intl.DateTimeFormat("en-GB").format(d));
console.log("Japanese:", new Intl.DateTimeFormat("ja-JP").format(d));
// Custom formatting (recommended)
const formatted = new Intl.DateTimeFormat("en-GB", {
year: "numeric",
month: "long",
day: "2-digit",
timeZone: "Europe/London"
}).format(d);
console.log("Custom:", formatted);Super useful for notifications, messages, and analytics dashboards.
Displaying relative time like '3 hours ago'
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
console.log(rtf.format(-3, "hour")); // "3 hours ago"
console.log(rtf.format(2, "day")); // "in 2 days"
console.log(rtf.format(-1, "week")); // "last week"
console.log(rtf.format(1, "month")); // "next month"
console.log(rtf.format(-5, "minute")); // "5 minutes ago"
// With numeric: "always"
const rtfNumeric = new Intl.RelativeTimeFormat("en", { numeric: "always" });
console.log(rtfNumeric.format(-1, "day")); // "1 d
...Timestamps avoid timezone issues completely.
Using timestamps to avoid timezone issues
// Get current timestamp
const now = Date.now();
console.log("Current timestamp:", now);
// Convert timestamp to date
const date = new Date(1700000000000);
console.log("From timestamp:", date.toISOString());
// Convert date to timestamp
console.log("To timestamp:", date.getTime());
// Add 30 minutes safely using timestamps
const later = new Date(now + 30 * 60 * 1000);
console.log("30 minutes later:", later.toISOString());The most common JavaScript date error. When you accept dates like "2025-01-01" from forms or APIs, JavaScript guesses the timezone.
Preventing the most common date error
// ❌ Ambiguous - may shift a day
const ambiguous = new Date("2025-01-01");
console.log("Ambiguous:", ambiguous.toString());
// ✔ Explicit UTC
const utc = new Date("2025-01-01T00:00:00Z");
console.log("Explicit UTC:", utc.toString());
// ✔ Explicit offset
const offset = new Date("2025-01-01T00:00:00+01:00");
console.log("With offset:", offset.toString());
// ✔ Best: split into UTC fields manually
const safe = new Date(Date.UTC(2025, 0, 1));
console.log("Date.UTC:", safe.toISOString());Safe date arithmetic functions
// Add days safely
function addDays(date, days) {
const d = new Date(date);
d.setDate(d.getDate() + days);
return d;
}
// Add months safely (auto-handles long/short months)
function addMonths(date, m) {
const d = new Date(date);
d.setMonth(d.getMonth() + m);
return d;
}
// Add years
function addYears(date, y) {
const d = new Date(date);
d.setFullYear(d.getFullYear() + y);
return d;
}
const today = new Date();
console.log("Today:", today.toDateString());
console.log("+7 days:
...How to validate user-entered dates
// Users might enter invalid dates like 2025-02-30
function isValidDate(y, m, d) {
const date = new Date(y, m - 1, d);
return (
date.getFullYear() === y &&
date.getMonth() === m - 1 &&
date.getDate() === d
);
}
console.log("2025-02-28 valid:", isValidDate(2025, 2, 28)); // true
console.log("2025-02-29 valid:", isValidDate(2025, 2, 29)); // false (not leap year)
console.log("2024-02-29 valid:", isValidDate(2024, 2, 29)); // true (leap year)
console.log("2025-02-30 valid:", isV
...A production-ready toolkit you can use anywhere:
A production-ready date toolkit
const TimeUtils = {
nowUTC: () => new Date().toISOString(),
fromUTC: (str) => new Date(str),
toUTC: (date) => new Date(date.getTime() - date.getTimezoneOffset() * 60000),
format: (date, options = {}) =>
new Intl.DateTimeFormat("en-GB", {
dateStyle: "medium",
timeStyle: "short",
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
...options,
}).format(date),
addDays: (date, d) => {
const next = new Date(date);
next.setDate(next.getDate
...Sign up for free to track which lessons you've completed and get learning reminders.