Working with Dates, Timezones & Intl API

Reviewed & published by Brayan K

By the end of this lesson you'll create, read, and format dates correctly — and show times, currencies, and "3 hours ago" the way each user's country expects.

Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

📚 Prerequisites: You should be comfortable with variables and functions. Knowing how objects and methods work will help, since a date is an object with methods.

💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:

🕰️ Real-World Analogy: A Date is like the single counter on a stopwatch that started ticking at midnight on Jan 1, 1970 (UTC):

1️⃣ The Date Object: One Number Underneath

A Date is JavaScript's built-in type for a moment in time. It does not store a time zone. It stores a single number: the milliseconds since the epoch (midnight, Jan 1 1970, UTC). Every other thing you see — local time, ISO strings, formatted text — is calculated from that number.

You create "now" with new Date(), read the raw number with getTime(), and get a universal string with toISOString().

// new Date() with no arguments = right now
  const now = new Date();

  console.log(now.getTime());      // ✅ Expected output: a big number, e.g. 1750000000000 (ms since 1970)
  console.log(typeof now.getTime()); // ✅ Expected output: number

  // toISOString() always prints in UTC with a trailing "Z"
  console.log(new Date(0).toISOString()); // ✅ Expected output: 1970-01-01T00:00:00.000Z

  // The same instant, two views:
  const launch = new Date("2025-06-16T12:00:00Z");
  console.log(launch.getTime());   // ✅ Expected output: 1750075200000
  console.log(launch.toISOString()); // ✅ Expected output: 2025-06-16T12:00:00.000Z

2️⃣ Creating & Parsing Dates Safely

There are three reliable ways to build a date. The safest take the guesswork out of which time zone JavaScript should assume. Notice the month is zero-indexed in the numeric constructor — 0 is January, 11 is December.

// 1) ISO string with "Z" = unambiguous UTC (recommended for stored data)
  const iso = new Date("2025-01-01T09:30:00Z");
  console.log(iso.toISOString()); // ✅ Expected output: 2025-01-01T09:30:00.000Z

  // 2) Date.UTC(year, monthIndex, day, ...) returns a timestamp in UTC
  const utc = new Date(Date.UTC(2025, 0, 1)); // month 0 = January!
  console.log(utc.toISOString()); // ✅ Expected output: 2025-01-01T00:00:00.000Z

  // 3) Numeric constructor uses LOCAL time (handy, but zone-dependent)
  const local = new Date(2025, 11, 25, 18, 0); // month 11 = December
  console.log(local.getFullYear(), local.getMonth()); // ✅ Expected output: 2025 11

  // ❌ Avoid: a bare date-only string is parsed as UTC midnight, then shown locally
  const risky = new Date("2025-01-01");
  console.log(risky.toISOString()); // ✅ Expected output: 2025-01-01T00:00:00.000Z

3️⃣ Reading & Changing Parts of a Date

Each piece has a get method and a matching set method: getFullYear(), getMonth(), getDate() (day of month), getDay() (day of week, 0 = Sunday), getHours(), and so on. The set methods mutate the date in place and conveniently roll over — adding a day to Jan 31 lands on Feb 1.

const d = new Date("2025-01-31T00:00:00Z");

  console.log(d.getUTCFullYear()); // ✅ Expected output: 2025
  console.log(d.getUTCMonth());    // ✅ Expected output: 0  (January is 0)
  console.log(d.getUTCDate());     // ✅ Expected output: 31

  // set methods MUTATE and roll over automatically
  d.setUTCDate(d.getUTCDate() + 1); // 31 + 1 spills into the next month
  console.log(d.toISOString());     // ✅ Expected output: 2025-02-01T00:00:00.000Z

🎯 Your Turn #1 — Build a Birthday

Remember: the month in Date.UTC() is zero-indexed. Fill in the blanks.

// 🎯 YOUR TURN — fill in the blanks marked with ___

  // 1) Build December 25, 2025 at midnight UTC.
  //    Months are zero-indexed, so December is 11.
  const xmas = new Date(Date.UTC(2025, ___, 25)); // 👉 replace ___ with the month index for December

  // 2) Read the day of the month back out.
  const day = xmas.getUTCDate(); // 👉 nothing to change here, just observe

  console.log(xmas.toISOString()); // ✅ Expected output: 2025-12-25T00:00:00.000Z
  console.log(day);                // ✅ Expected output: 25

4️⃣ Timestamps: Bug-Free Date Math

A timestamp is just that millisecond number. Because it's plain arithmetic, adding fixed durations (minutes, hours) is reliable. Date.now() gives the current timestamp directly. To add calendar units like days or months, prefer the set methods, which respect month lengths and daylight saving.

const start = new Date("2025-06-16T12:00:00Z");

  // Add 90 minutes by working in milliseconds
  const later = new Date(start.getTime() + 90 * 60 * 1000);
  console.log(later.toISOString()); // ✅ Expected output: 2025-06-16T13:30:00.000Z

  // Difference between two dates, in days
  const a = new Date("2025-01-01T00:00:00Z");
  const b = new Date("2025-01-08T00:00:00Z");
  const days = (b.getTime() - a.getTime()) / (1000 * 60 * 60 * 24);
  console.log(days); // ✅ Expected output: 7

  // Add 3 calendar months safely with setUTCMonth
  const planned = new Date("2025-01-15T00:00:00Z");
  planned.setUTCMonth(planned.getUTCMonth() + 3);
  console.log(planned.toISOString()); // ✅ Expected output: 2025-04-15T00:00:00.000Z

5️⃣ Formatting with Intl.DateTimeFormat

Never build a display string by hand. Intl.DateTimeFormat takes a locale (like "en-US" or "en-GB") and an options object, then prints the date the way that country writes it — correct order, month names, and 12- vs 24-hour clock. Pass timeZone to control which zone it renders in.

const d = new Date("2025-06-16T18:05:00Z");

  // US puts the month first; UK puts the day first
  console.log(new Intl.DateTimeFormat("en-US").format(d)); // ✅ Expected output: 6/16/2025
  console.log(new Intl.DateTimeFormat("en-GB").format(d)); // ✅ Expected output: 16/06/2025

  // Spell it out with options + a fixed time zone
  const long = new Intl.DateTimeFormat("en-GB", {
    weekday: "long",
    day: "numeric",
    month: "long",
    year: "numeric",
    timeZone: "UTC",
  }).format(d);
  console.log(long); // ✅ Expected output: Monday 16 June 2025

Intl.NumberFormat — Money & Numbers

The same family formats numbers. With style: "currency" and a currency code, Intl.NumberFormat adds the right symbol, decimals, and grouping for the locale. This is how you show prices correctly to a global audience.

const price = 1234.5;

  // US dollars
  console.log(new Intl.NumberFormat("en-US", {
    style: "currency", currency: "USD",
  }).format(price)); // ✅ Expected output: $1,234.50

  // British pounds
  console.log(new Intl.NumberFormat("en-GB", {
    style: "currency", currency: "GBP",
  }).format(price)); // ✅ Expected output: £1,234.50

  // Plain grouped number
  console.log(new Intl.NumberFormat("en-US").format(1000000)); // ✅ Expected output: 1,000,000

7️⃣ Intl.RelativeTimeFormat — "3 hours ago"

For feeds and notifications, show relative time. Negative numbers are the past, positive are the future. With numeric: "auto" it even says "yesterday" and "tomorrow" where a language has those words.

const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });

  console.log(rtf.format(-3, "hour")); // ✅ Expected output: 3 hours ago
  console.log(rtf.format(2, "day"));   // ✅ Expected output: in 2 days
  console.log(rtf.format(-1, "day"));  // ✅ Expected output: yesterday
  console.log(rtf.format(1, "week"));  // ✅ Expected output: next week

🎯 Your Turn #2 — Price Tag

Use Intl.NumberFormat to print a euro price for a French audience.

// 🎯 YOUR TURN — fill in the blanks marked with ___

  const amount = 49.99;

  // 1) Choose the currency code for euros (3 letters, uppercase).
  const euros = new Intl.NumberFormat("de-DE", {
    style: "currency",
    currency: "___", // 👉 replace ___ with the ISO code for the euro
  }).format(amount);

  console.log(euros); // ✅ Expected output: 49,99 €  (German formats euro after the number)

🧩 Mini-Challenge — Event Countdown

Now with the scaffolding removed. Work from the comment outline only.

// 🧩 MINI-CHALLENGE: Days until New Year 2026
  // 1. Create a Date for the event: 2026-01-01 at midnight UTC (use an ISO string with "Z").
  // 2. Create a Date for a fixed "today": 2025-12-16 at midnight UTC.
  // 3. Subtract their getTime() values and divide by the ms in one day (1000 * 60 * 60 * 24).
  // 4. Log the whole number of days using Math.round(...).
  //
  // ✅ Expected output: 16

  // your code here

Time Zones in Practice

Store the instant in UTC; convert only when you display. Pass an IANA zone name (like "America/New_York") to timeZone and the formatter handles the offset and daylight saving for you.

const instant = new Date("2025-06-16T16:00:00Z");

  const show = (zone) =>
    new Intl.DateTimeFormat("en-GB", {
      hour: "2-digit",
      minute: "2-digit",
      timeZone: zone,
    }).format(instant);

  console.log("London:", show("Europe/London"));     // ✅ Expected output: London: 17:00 (BST in summer)
  console.log("New York:", show("America/New_York")); // ✅ Expected output: New York: 12:00
  console.log("Tokyo:", show("Asia/Tokyo"));           // ✅ Expected output: Tokyo: 01:00

  // Discover the visitor's own zone:
  console.log(Intl.DateTimeFormat().resolvedOptions().timeZone); // ✅ Expected output: e.g. Europe/London

📦 A Note on Date Libraries: The built-in Date is fine for the basics, but it is mutable and quirky. Two things worth knowing:

Common Errors & Fixes

Quick Reference — Dates & Intl

TaskCode
Nownew Date() / Date.now()
From ISO (UTC)new Date("2025-06-16T12:00:00Z")
From parts (UTC)new Date(Date.UTC(2025, 0, 1))
Raw timestampd.getTime()
Universal stringd.toISOString()
Read partd.getUTCFullYear(), getUTCMonth() (0–11)
Format datenew Intl.DateTimeFormat("en-GB", {…}).format(d)
Format currencynew Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })
Relative timenew Intl.RelativeTimeFormat("en", { numeric: "auto" }).format(-3, "hour")

Lesson Complete — Dates & Internationalization!

You can now handle dates the way production apps do — accurately, and in any language. 🌍

Practice quiz

What single value does a Date store internally?

  • A formatted local string
  • A year, month, and day struct
  • Milliseconds since the 1970 UTC epoch
  • A time-zone name

Answer: Milliseconds since the 1970 UTC epoch. A Date is one number: milliseconds since midnight Jan 1 1970 UTC; everything else is calculated from it.

In JavaScript Date, which month index represents December?

  • 11
  • 12
  • 1
  • 0

Answer: 11. Months are zero-indexed: January is 0 and December is 11.

What does new Date(0).toISOString() produce?

  • 2000-01-01T00:00:00.000Z
  • The current date
  • Invalid Date
  • 1970-01-01T00:00:00.000Z

Answer: 1970-01-01T00:00:00.000Z. Timestamp 0 is the Unix epoch: 1970-01-01T00:00:00.000Z.

Which is the recommended, unambiguous way to store a date?

  • A bare date-only string like '2025-01-01'
  • An ISO string with a 'Z', or Date.UTC()
  • new Date(year, month, day) in local time
  • A formatted display string

Answer: An ISO string with a 'Z', or Date.UTC(). ISO strings with Z (or Date.UTC) are unambiguous UTC; bare date-only strings can shift by a day locally.

What is the difference between Date.now() and someDate.getTime()?

  • Date.now() is static and gives the current time; getTime() reads an existing Date
  • They return different units
  • getTime() returns a string
  • Date.now() needs a new Date first

Answer: Date.now() is static and gives the current time; getTime() reads an existing Date. Both return milliseconds since the epoch; Date.now() is static, getTime() is called on an instance.

Do Date set methods like setUTCDate mutate the original Date?

  • No, they return a new Date
  • Only in strict mode
  • Yes, they mutate it in place
  • Only for UTC methods

Answer: Yes, they mutate it in place. set methods mutate the date in place (and conveniently roll over month boundaries).

Which Intl object formats a date for a specific locale?

  • Intl.NumberFormat
  • Intl.DateTimeFormat
  • Intl.Collator
  • Intl.ListFormat

Answer: Intl.DateTimeFormat. Intl.DateTimeFormat takes a locale and options and prints the date the way that country writes it.

To format a value as currency, which Intl API and option do you use?

  • Intl.DateTimeFormat with style 'currency'
  • Intl.RelativeTimeFormat
  • Number.toCurrency()
  • Intl.NumberFormat with style 'currency' and a currency code

Answer: Intl.NumberFormat with style 'currency' and a currency code. Intl.NumberFormat with { style: 'currency', currency: 'USD' } adds the right symbol and grouping.

With Intl.RelativeTimeFormat, what does a negative number represent?

  • The future
  • The past
  • An invalid value
  • Midnight

Answer: The past. Negative numbers are the past (e.g. -3 'hour' is '3 hours ago'); positive numbers are the future.

What is the recommended strategy for handling time zones?

  • Store local time, convert on the server
  • Always store the user's offset in minutes
  • Store the instant in UTC, convert only when displaying
  • Avoid UTC entirely

Answer: Store the instant in UTC, convert only when displaying. Store instants in UTC and convert to the user's zone only at display time via a timeZone option.

Continue this course

Frequently asked questions

Why is the month 11 for December?

JavaScript months are zero-indexed: January is 0 and December is 11. Always subtract 1 when you build a date from a human month number, and add 1 when you read getMonth() back for display.

Why does new Date('2025-01-01') sometimes show December 31?

A date-only string like '2025-01-01' is parsed as midnight UTC, then displayed in your local time. If you are behind UTC (e.g. New York), midnight UTC is the evening of the day before, so it looks 'one day off'. Add an explicit time and zone, or use Date.UTC().

What's the difference between getTime() and Date.now()?

Both return milliseconds since the Unix epoch (Jan 1 1970 UTC). Date.now() is a static method that gives the current time without creating a Date object; getTime() is called on an existing Date instance.

How do I format money for different countries?

Use Intl.NumberFormat with style: 'currency' and a currency code like 'USD' or 'GBP'. It picks the right symbol, decimal separators, and digit grouping for the locale you pass.

Should I use a date library like date-fns instead?

For simple formatting and arithmetic the built-in Date plus Intl is enough. For heavy parsing, time-zone math, or durations, a library such as date-fns is safer. The upcoming Temporal API aims to replace Date entirely with an immutable, time-zone-aware design.

Related lessons