Ad – 728Γ—90
πŸ”§ Intermediate JS

JavaScript Date and Time – Working with Dates in JavaScript

The JavaScript Date object has been around since the very beginning of the language β€” and it shows. It has some notorious quirks (months start at 0!) but with the right knowledge, you can handle dates, times, formatting, and arithmetic reliably. This lesson also covers the modern Intl API for locale-aware formatting.

⏱️ 22 min read 🎯 Intermediate πŸ“… Updated 2026

Creating Date Objects

JavaScript
// Current date and time
const now = new Date();
console.log(now); // current timestamp

// From a string (ISO 8601 recommended)
const d1 = new Date('2026-06-15');              // date only (midnight UTC)
const d2 = new Date('2026-06-15T10:30:00');     // local time
const d3 = new Date('2026-06-15T10:30:00Z');    // UTC explicitly
const d4 = new Date('2026-06-15T10:30:00+05:30'); // UTC+5:30 (IST)

// From timestamp (milliseconds since Unix epoch: Jan 1 1970 UTC)
const d5 = new Date(0);          // 1970-01-01T00:00:00.000Z
const d6 = new Date(1000);       // 1970-01-01T00:00:01.000Z
const d7 = new Date(Date.now()); // now (same as new Date())

// From year, month, day, ... (month is 0-indexed!)
const d8 = new Date(2026, 5, 15);          // June 15, 2026 local time
//                        ↑ 5 = June (0=Jan, 1=Feb, ..., 11=Dec)
const d9 = new Date(2026, 0, 1, 9, 30, 0); // Jan 1, 2026 09:30:00

// Invalid date
const invalid = new Date('not a date');
console.log(isNaN(invalid)); // true
console.log(invalid.toString()); // 'Invalid Date'

// Date.now() – fastest way to get current timestamp (no object creation)
const timestamp = Date.now(); // number of milliseconds
console.log(typeof timestamp); // 'number'
πŸ’‘
Month Indexing Gotcha

In JavaScript's Date constructor and getMonth(), months are zero-indexed: January = 0, February = 1, ..., December = 11. This is one of the most common sources of off-by-one bugs. Always remember to add 1 when displaying the month: date.getMonth() + 1.

Getting Date Parts

JavaScript
const d = new Date('2026-06-15T14:30:45.500');

// All getters return LOCAL time (unless using UTC variants)
console.log(d.getFullYear());     // 2026
console.log(d.getMonth());        // 5  ← June (0-indexed!)
console.log(d.getMonth() + 1);    // 6  ← human-readable
console.log(d.getDate());         // 15 (day of month)
console.log(d.getDay());          // day of week: 0=Sun, 1=Mon, ..., 6=Sat
console.log(d.getHours());        // 14
console.log(d.getMinutes());      // 30
console.log(d.getSeconds());      // 45
console.log(d.getMilliseconds()); // 500
console.log(d.getTime());         // Unix timestamp in ms

// UTC variants (always in UTC regardless of local timezone)
console.log(d.getUTCHours());     // could differ from getHours()
console.log(d.getUTCMonth());     // also 0-indexed

// Compute age from birthdate
function getAge(birthDate) {
  const today = new Date();
  let age = today.getFullYear() - birthDate.getFullYear();
  const m = today.getMonth() - birthDate.getMonth();
  if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
    age--;
  }
  return age;
}
const age = getAge(new Date(1998, 2, 15)); // March 15, 1998
console.log(age); // 28 (in 2026)

Setting Date Parts

JavaScript
const d = new Date('2026-06-15');

d.setFullYear(2027);   // change year
d.setMonth(11);         // change to December (0-indexed)
d.setDate(31);          // change day of month
d.setHours(9);          // change hour
d.setMinutes(30);
d.setSeconds(0);
d.setMilliseconds(0);

console.log(d); // 2027-12-31T09:30:00.000...

// JavaScript auto-adjusts overflow dates
const overflow = new Date(2026, 1, 30); // Feb 30 doesn't exist
console.log(overflow.toISOString()); // Automatically becomes Mar 2, 2026

// Set to start/end of day
function startOfDay(date) {
  const d = new Date(date);
  d.setHours(0, 0, 0, 0); // hours, minutes, seconds, milliseconds
  return d;
}
function endOfDay(date) {
  const d = new Date(date);
  d.setHours(23, 59, 59, 999);
  return d;
}

Date Arithmetic

Dates are stored as milliseconds internally. Arithmetic is done by converting to milliseconds, calculating, and converting back.

JavaScript
const MS_PER_DAY = 1000 * 60 * 60 * 24;

// Days between two dates
function daysBetween(date1, date2) {
  const ms = Math.abs(date2.getTime() - date1.getTime());
  return Math.round(ms / MS_PER_DAY);
}
const start = new Date('2026-01-01');
const end   = new Date('2026-06-15');
console.log(daysBetween(start, end)); // 165 days

// Add N days to a date
function addDays(date, days) {
  const result = new Date(date);
  result.setDate(result.getDate() + days);
  return result;
}
const tomorrow = addDays(new Date(), 1);
const nextWeek = addDays(new Date(), 7);

// Add months (handles variable month lengths)
function addMonths(date, months) {
  const result = new Date(date);
  result.setMonth(result.getMonth() + months);
  return result;
}

// Compare dates (convert to timestamps for comparison)
const d1 = new Date('2026-01-01');
const d2 = new Date('2026-12-31');
console.log(d1 < d2);            // true
console.log(d1.getTime() === d2.getTime()); // false

// Is a date in the past?
const isPast = (date) => date.getTime() < Date.now();

Formatting Dates

JavaScript
const d = new Date('2026-06-15T14:30:00');

// toLocaleDateString / toLocaleTimeString / toLocaleString
console.log(d.toLocaleDateString('en-GB')); // '15/06/2026'
console.log(d.toLocaleDateString('en-US')); // '6/15/2026'
console.log(d.toLocaleDateString('ja-JP')); // '2026/6/15'

console.log(d.toLocaleTimeString('en-US', { hour12: true }));
// '2:30:00 PM'

console.log(d.toLocaleString('en-GB', {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit'
}));
// 'Monday, 15 June 2026 at 14:30'

// Intl.DateTimeFormat – for repeated formatting (more efficient)
const formatter = new Intl.DateTimeFormat('en-US', {
  year: 'numeric',
  month: 'short',
  day: '2-digit',
  timeZone: 'America/New_York'
});
const dates = [new Date('2026-01-15'), new Date('2026-06-15')];
dates.forEach(date => console.log(formatter.format(date)));
// Jan 15, 2026
// Jun 15, 2026

// Relative time formatting
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
console.log(rtf.format(-1, 'day'));    // 'yesterday'
console.log(rtf.format(3, 'day'));     // 'in 3 days'
console.log(rtf.format(-2, 'week'));   // '2 weeks ago'
β–Ά Output
15/06/2026
6/15/2026
2026/6/15
2:30:00 PM
Monday, 15 June 2026 at 14:30
Jan 15, 2026
Jun 15, 2026
yesterday
in 3 days
2 weeks ago

Timezone Considerations

JavaScript
// JavaScript Date always stores UTC internally
// Display methods (getHours, toLocaleDateString) use local timezone

const d = new Date('2026-06-15T00:00:00Z'); // midnight UTC
console.log(d.getHours());       // could be 23, 1, etc. based on local TZ
console.log(d.getUTCHours());    // always 0

// Get local timezone offset (minutes from UTC; negative means ahead of UTC)
const offsetMinutes = new Date().getTimezoneOffset();
console.log(offsetMinutes); // e.g., -60 for UTC+1 (BST), 330 for IST

// Format in a specific timezone with Intl
const tokyoTime = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Tokyo',
  hour: '2-digit',
  minute: '2-digit',
  hour12: false
}).format(new Date());
console.log(`Tokyo: ${tokyoTime}`);

// Best practice: store/transmit dates as UTC ISO strings
const event = {
  title: 'Conference',
  startAt: new Date('2026-09-01T09:00:00Z').toISOString()
  // '2026-09-01T09:00:00.000Z'
};
πŸ’‘
Use date-fns for Complex Date Logic

For recurring events, business days, calendar grids, or complex timezone handling, reach for date-fns or Temporal (the upcoming built-in replacement for the Date API). The date-fns library is tree-shakeable, immutable, and covers virtually every date use case.

MethodReturnsNote
getFullYear()4-digit yearUse this, not deprecated getYear()
getMonth()0–11Add 1 for human display
getDate()1–31Day of month
getDay()0–60 = Sunday, 6 = Saturday
getHours()0–23Local time
getTime()ms since epochUse for arithmetic and comparison
Date.now()ms since epochStatic, no object needed
toISOString()ISO 8601 stringAlways UTC

Interview Questions

  • Why does getMonth() return 0 for January?
  • How do you calculate the number of days between two dates?
  • What is the difference between getHours() and getUTCHours()?
  • How do you format a date for display in a specific locale?
  • Why is Date.now() preferred over new Date().getTime()?

πŸ‹οΈ Practical Exercise

Write a countdown(targetDate) function that returns an object { days, hours, minutes, seconds } representing the time remaining until the target date. If the target date has passed, return all zeros. Test it with a date 7 days in the future.

πŸ”₯ Challenge Exercise

Build a Calendar class that, given a year and month, generates a 2D array representing the month's grid (rows = weeks, cols = days Sun–Sat). Each cell should contain either null (empty) or a date object for that day. Ensure the first row correctly aligns to the right day of the week. Display the grid as a formatted string.

Frequently Asked Questions

Why is working with dates in JavaScript so error-prone?
Several quirks combine: months are 0-indexed (Jan = 0), parsing behaviour varies by browser, local timezone vs UTC confusion is easy to make, and DST transitions can cause dates to shift. Libraries like date-fns or the upcoming Temporal API address these issues.
What is the Temporal API?
Temporal is the upcoming TC39 proposal to replace the Date object. It has immutable date/time objects, proper timezone support, nanosecond precision, and no 0-indexed months. As of 2026, it is at Stage 3 and available behind flags in some environments.
How do I check if two Date objects represent the same day (ignoring time)?
Compare the year, month, and date parts: a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(). Alternatively, compare their ISO date strings: a.toISOString().slice(0, 10) === b.toISOString().slice(0, 10).
Why does new Date('2026-06-15') parse as midnight UTC while new Date('June 15 2026') parses as local time?
ISO 8601 date-only strings are treated as UTC by the specification. Non-ISO strings are implementation-dependent and many engines treat them as local time. Always use ISO format or explicit timestamps to avoid ambiguity.
What is Date.now() useful for?
Any situation where you need a timestamp without creating a Date object: performance timing (const start = Date.now()), generating unique IDs, recording event times, or measuring elapsed time. It is slightly faster than new Date().getTime().
Ad – 336Γ—280

πŸ“‹ Summary

  • Create dates with new Date(), ISO strings, timestamps, or year/month/day arguments.
  • getMonth() is 0-indexed β€” January is 0, December is 11. Always add 1 for display.
  • Use Date.now() for the current timestamp without creating a Date object.
  • Date arithmetic is done in milliseconds: convert to .getTime(), calculate, convert back.
  • Use toLocaleDateString(locale, options) or Intl.DateTimeFormat for locale-aware formatting.
  • Use Intl.RelativeTimeFormat for "3 days ago" / "in 2 hours" style formatting.
  • Always store and transmit dates as UTC ISO strings to avoid timezone confusion.
  • For complex date logic, use date-fns or wait for the Temporal API.