Skip to content
aviral gupta

// I5.3 · ~35 min · Intermediate

Dates and times with Date

After this lesson you create a date without the off-by-one month, turn it into a timestamp or an ISO string and back, and calculate and compare dates safely.

Lesson 3 of 5 in I5 Working with data

You will be able to

  • Create dates with Date.UTC and new Date, remembering that months count from 0
  • Read and write timestamps and ISO strings, and know which strings mean UTC
  • Calculate with dates in milliseconds and compare them correctly
  1. Warm-up · Activity 1 of 7

    Warm-up from lesson I5.2. What does this print?

    const [, year, month] = "2026-10-05".match(/(\d{4})-(\d{2})/);
    console.log(Number(month));
  2. Predict · Activity 2 of 7

    Predict before you read on. The month argument is 10. What does this print?

    const d = new Date(Date.UTC(2026, 10, 5));
    console.log(d.toISOString().slice(0, 10));
  3. Practice · Activity 3 of 7

    Fill in the number of milliseconds in one day, so that this prints 1.

    const ms = Date.UTC(2026, 9, 6) - Date.UTC(2026, 9, 5);
    console.log(ms / ____);
    console.log(ms /);
  4. Practice · Activity 4 of 7

    Match each call to what it gives.

  5. Practice · Activity 5 of 7

    A date-only string. What does this print?

    const d = new Date("2026-10-05");
    console.log(d.getUTCHours(), d.toISOString());
  6. Brain teaser · Activity 6 of 7

    Brain teaser. On a computer in Berlin, where October is UTC+2. The string has a time but no Z. What does this print?

    const d = new Date("2026-10-05T00:00");
    console.log(d.toISOString());
  7. Apply · Activity 7 of 7

    Mini-task: write daysBetween(from, to) for two date-only strings such as "2026-10-05" and "2026-12-24". Print the days to go, the weekday of the second date with getUTCDay() and a list of day names, and the date of a reminder one week before it as YYYY-MM-DD.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

One meeting, seven date questions

A meeting starts on 5 October 2026 at 14:30 UTC, built with Date.UTC and month 9. The program prints it as an ISO string, as its month number and as a timestamp, then parses two ISO strings. It adds 90 minutes and counts the days to 24 December in milliseconds, shows that 31 February carries over, compares dates, and recognises an Invalid Date. Every line uses UTC, so the output is the same on every computer.

main.js

// 1. Months count from 0: 9 is October.
const start = new Date(Date.UTC(2026, 9, 5, 14, 30));
console.log("start:", start.toISOString());
console.log("getUTCMonth:", start.getUTCMonth(), "-> month number", start.getUTCMonth() + 1);

// 2. A Date is a timestamp: milliseconds since 1970-01-01 UTC.
console.log("timestamp:", start.getTime());
console.log("timestamp 0:", new Date(0).toISOString());

// 3. Parsing ISO strings: date-only means UTC; Z or +02:00 say it exactly.
console.log(new Date("2026-10-05").toISOString());
console.log(new Date("2026-10-05T14:30:00+02:00").toISOString());

// 4. Arithmetic in milliseconds.
const MINUTE = 60 * 1000;
const DAY = 24 * 60 * MINUTE;
const end = new Date(start.getTime() + 90 * MINUTE);
console.log("90 minutes later:", end.toISOString());
console.log("days to 24 December:", (Date.UTC(2026, 11, 24) - Date.UTC(2026, 9, 5)) / DAY);

// 5. Overflow carries over: 31 February becomes 3 March.
console.log(new Date(Date.UTC(2026, 1, 31)).toISOString().slice(0, 10));

// 6. Comparing: < works on timestamps, === compares objects.
const a = new Date("2026-10-05");
const b = new Date("2026-10-05");
console.log(a < end, a === b, a.getTime() === b.getTime());

// 7. A string that is not a date gives an Invalid Date.
const bad = new Date("2026-13-01");
console.log("invalid:", Number.isNaN(bad.getTime()));

Run it with

node main.js

Output

start: 2026-10-05T14:30:00.000Z
getUTCMonth: 9 -> month number 10
timestamp: 1791210600000
timestamp 0: 1970-01-01T00:00:00.000Z
2026-10-05T00:00:00.000Z
2026-10-05T12:30:00.000Z
90 minutes later: 2026-10-05T16:00:00.000Z
days to 24 December: 80
2026-03-03
true false true
invalid: true
  • getUTCMonth() gave 9 for October; add 1 only when you show the month to people.
  • +02:00 in the string moved the time two hours back to UTC.
  • a === b was false for the same date: === asks whether they are the same object.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Exercises

Exercise 1 of 2

Add days to a date

addDays(isoDate, days) gets a date-only string such as "2026-10-05" and returns the date that many days later, as "YYYY-MM-DD". Negative days go back. The starter passes the month from the string straight to Date.UTC, so every result lands a month too late. Fix it.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    Run the tests: 2026-10-05 plus 7 gives a date in November. Which number is off?

  2. Hint 2

    The string says 10 for October, but Date.UTC counts months from 0.

  3. Hint 3

    new Date(Date.UTC(year, month - 1, day + days))

Show a solution

One way to solve it. Yours can look different and still pass the checks.

export function addDays(isoDate, days) {
  const [year, month, day] = isoDate.split("-").map(Number);
  const date = new Date(Date.UTC(year, month - 1, day + days));
  return date.toISOString().slice(0, 10);
}
Run it on your computer

Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

export function addDays(isoDate, days) {
  const [year, month, day] = isoDate.split("-").map(Number);
  const date = new Date(Date.UTC(year, month, day + days));
  return date.toISOString().slice(0, 10);
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {addDays} from './main.js';

test('2026-10-05 plus 7 days is 2026-10-12', () => {
  const got = addDays('2026-10-05', 7);
  assert.equal(got, '2026-10-12', `addDays('2026-10-05', 7) gave ${got}`);
});

test('Adding days crosses the end of a month', () => {
  const got = addDays('2026-01-30', 3);
  assert.equal(got, '2026-02-02', `addDays('2026-01-30', 3) gave ${got}`);
});

test('Negative days go back', () => {
  const got = addDays('2026-03-01', -1);
  assert.equal(got, '2026-02-28', `addDays('2026-03-01', -1) gave ${got}`);
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Exercise 2 of 2

Sort events by time

sortByTime(events) gets objects like {title: "Call", at: "2026-10-05T14:30:00+02:00"} and returns a new array sorted from the earliest moment to the latest, without changing the input. The starter compares the strings, which is wrong when the offsets differ: 14:30+02:00 is 12:30 UTC. Compare the moments instead.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    Run the tests: Call comes after Lunch. Is the string "14:30+02:00" later than "13:00Z" as a moment?

  2. Hint 2

    new Date(a.at) understands the offset. Subtracting two Dates gives the difference in milliseconds.

  3. Hint 3

    return [...events].sort((a, b) => new Date(a.at) - new Date(b.at));

Show a solution

One way to solve it. Yours can look different and still pass the checks.

export function sortByTime(events) {
  return [...events].sort((a, b) => new Date(a.at) - new Date(b.at));
}
Run it on your computer

Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

export function sortByTime(events) {
  return [...events].sort((a, b) => a.at.localeCompare(b.at));
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {sortByTime} from './main.js';

const events = [
  {title: 'Lunch', at: '2026-10-05T13:00:00Z'},
  {title: 'Call', at: '2026-10-05T14:30:00+02:00'},
  {title: 'Breakfast', at: '2026-10-05T07:00:00Z'}
];
const titles = (list) => list.map((e) => e.title).join(', ');

test('Events with Z are sorted by time', () => {
  const got = sortByTime([events[0], events[2]]);
  assert.equal(titles(got), 'Breakfast, Lunch', `the order is ${titles(got)}`);
});

test('The offset counts: 14:30+02:00 is before 13:00 UTC', () => {
  const got = sortByTime(events);
  assert.equal(titles(got), 'Breakfast, Call, Lunch', `the order is ${titles(got)}`);
});

test('The input array is not changed', () => {
  const input = [...events];
  sortByTime(input);
  assert.equal(titles(input), 'Lunch, Call, Breakfast', `the input became ${titles(input)}`);
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Common mistakes

Parsing a date in German format

const due = new Date("31.12.2026");
console.log(due.toISOString());

What Node.js prints

RangeError: Invalid time value

Why, and the fix

Only the ISO format, such as "2026-12-31", is guaranteed to work. "31.12.2026" gives an Invalid Date, and toISOString() on an Invalid Date throws. Take the parts apart yourself and use Date.UTC(2026, 11, 31), or convert the text to ISO first. Check a parsed date with Number.isNaN(date.getTime()).

Treating Date.UTC as a Date

const due = Date.UTC(2026, 9, 5);
console.log(due.toISOString());

What Node.js prints

TypeError: due.toISOString is not a function

Why, and the fix

Date.UTC returns a number, the timestamp, not a Date object, so it has no Date methods. Wrap it: const due = new Date(Date.UTC(2026, 9, 5)). Date.now() is a number too.

Expecting a Date back from JSON

const saved = JSON.stringify({ due: new Date(Date.UTC(2026, 9, 5)) });
const task = JSON.parse(saved);
console.log(task.due.getUTCFullYear());

What Node.js prints

TypeError: task.due.getUTCFullYear is not a function

Why, and the fix

JSON has no date type. JSON.stringify writes a Date as its ISO string, and JSON.parse gives that string back, not a Date. Turn it into a Date again after parsing: new Date(task.due).getUTCFullYear() is 2026.

JavaScript in the browser: your browser’s own engine, in a sandboxed worker. Syntax errors are located with acorn 8.18.0, MIT. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

A Date is a timestamp

Inside, a Date is one number: milliseconds since midnight at the start of 1 January 1970, UTC. getTime() and Date.now() give that number. What you see depends on how it is read: getHours(), getDate() and toString() use the computer’s time zone, getUTCHours(), getUTCDate() and toISOString() use UTC, so they print the same on every machine. Months count from 0: Date.UTC(2026, 9, 5) is 5 October, and December is 11. Values that are too large carry over: day 31 of February is in March. MDN now calls Date a legacy feature next to the newer Temporal API, but Temporal is not available everywhere yet, and Node.js 24 does not have it.

Creating and parsing

Date.UTC(year, monthIndex, day, hours, minutes) returns a timestamp for that moment in UTC; wrap it in new Date(...) to get a Date. new Date(year, monthIndex, day) reads the same numbers as local time, so the moment differs from computer to computer. Strings: use the ISO format. A date-only string such as "2026-10-05" is read as UTC midnight. A date-time string without an offset, "2026-10-05T14:30", is read as local time; add Z or +02:00 to make it exact. toISOString() always writes UTC with a Z, and JSON.stringify writes a Date the same way, as a string.

Calculating and comparing

Do date arithmetic on timestamps: new Date(date.getTime() + 90 * 60 * 1000) is 90 minutes later, and (later - earlier) / (24 * 60 * 60 * 1000) is the number of days between two UTC midnights. Subtracting two Dates gives milliseconds. a < b works, because comparison turns both into numbers, but a === b compares two objects and is false for two equal dates: compare a.getTime() === b.getTime(). Sort with (a, b) => a - b. A string that is not a valid date gives an Invalid Date, whose getTime() is NaN and whose toISOString() throws a RangeError.

Sources

Last reviewed October 5, 2026