Lesson 18 โข Intermediate
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.
๐ 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:
.html file with <script> tags and open it in your browser๐ฐ๏ธ Real-World Analogy: A Date is like the single counter on a stopwatch that started ticking at midnight on Jan 1, 1970 (UTC):
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().
See the single number behind every Date
// 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()); //
...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.
Explicit, ISO, and numeric constructors
// 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 =
...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.
Read fields and let set methods roll over
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.000ZRemember: the month in Date.UTC() is zero-indexed. Fill in the blanks.
Fill in the blanks marked with ___
// ๐ฏ 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 outp
...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.
Add durations and measure gaps
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 sa
...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.
Same instant, formatted for different countries
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); //
...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.
Format money for different locales
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,000For 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.
Human-friendly time differences
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 weekUse Intl.NumberFormat to print a euro price for a French audience.
Fill in the blanks marked with ___
// ๐ฏ 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)Now with the scaffolding removed. Work from the comment outline only.
Only a comment outline โ you write the code
// ๐งฉ 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 hereStore 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.
Render a UTC moment in several zones
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 visi
...๐ฆ A Note on Date Libraries: The built-in Date is fine for the basics, but it is mutable and quirky. Two things worth knowing:
addDays, format, differenceInDays) that never mutate your dates.Date with immutable, time-zone-aware types. It's rolling out now; learn it when it lands in your runtime.new Date(2025, 12, 1); // ๐ month 12 rolls into JANUARY 2026!
new Date(2025, 11, 1); // โ December: months are 0-indexed (0โ11)new Date("2025-01-01"); // parsed as UTC midnight, shown locally โ may read Dec 31
new Date("2025-01-01T00:00:00"); // โ add a time, or use Date.UTC(2025, 0, 1)const start = new Date("2025-01-01T00:00:00Z");
const end = start; // ๐ same object, not a copy!
end.setUTCDate(10); // this also changes "start"
const copy = new Date(start); // โ clone first, then mutate the copyconst d = new Date("not a date");
console.log(isNaN(d.getTime())); // โ true โ check this before trusting the date// Output depends on the server/visitor's zone โ flaky in tests
new Intl.DateTimeFormat("en-GB").format(d);
// โ pin it: pass { timeZone: "UTC" } (or the zone you mean)| Task | Code |
|---|---|
| Now | new Date() / Date.now() |
| From ISO (UTC) | new Date("2025-06-16T12:00:00Z") |
| From parts (UTC) | new Date(Date.UTC(2025, 0, 1)) |
| Raw timestamp | d.getTime() |
| Universal string | d.toISOString() |
| Read part | d.getUTCFullYear(), getUTCMonth() (0โ11) |
| Format date | new Intl.DateTimeFormat("en-GB", {โฆ}).format(d) |
| Format currency | new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }) |
| Relative time | new Intl.RelativeTimeFormat("en", { numeric: "auto" }).format(-3, "hour") |
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.
Date is one number: milliseconds since the 1970 UTC epochZ or Date.UTC()set methods for calendar unitsIntl.DateTimeFormat, Intl.NumberFormat, and Intl.RelativeTimeFormatYou can now handle dates the way production apps do โ accurately, and in any language. ๐
Sign up for free to track which lessons you've completed and get learning reminders.