Date and time

Time is one of those things every program eventually has to deal with: when a post was created, how long a task ran, what “next Tuesday” means. JavaScript hands you a single built-in for all of it — the Date object. It stores a moment in time and gives you methods to read, change, format, and compare it.

You’ll reach for it to stamp creation and modification times, to measure how long some code takes, or to print today’s date on a page.

One idea sits under everything else on this page, so meet it first: internally a Date is just a number. It stores the count of milliseconds since a fixed reference moment. Every method you’ll see is a convenient view onto that one number.

…negative…
0
86400000
…now ≈ 1.75e12…
before 1970
1 Jan 1970 UTC
2 Jan 1970
today
A Date holds a single number — milliseconds counted from the 1970 epoch. Everything else is derived from it.

Creation

You build a Date by calling new Date(). What you pass decides which moment it points at.

No arguments — right now

Call it empty and you get a Date for the current date and time:

let now = new Date();
alert( now ); // shows current date/time

A number of milliseconds — the timestamp

Pass a single number and it’s treated as the count of milliseconds (1/1000 of a second) that have elapsed since midnight on January 1st, 1970, UTC+0. That reference instant has a name: the epoch.

// 0 means 01.01.1970 UTC+0
let Jan01_1970 = new Date(0);
alert( Jan01_1970 );

// now add 24 hours, get 02.01.1970 UTC+0
let Jan02_1970 = new Date(24 * 3600 * 1000);
alert( Jan02_1970 );

That integer — milliseconds since the beginning of 1970 — is called a timestamp. It’s a compact, purely numeric way to name any instant. You can build a date from a timestamp with new Date(timestamp), and go the other way, turning an existing Date back into its timestamp, with date.getTime() (covered below).

Dates before the epoch have negative timestamps:

// 31 Dec 1969
let Dec31_1969 = new Date(-24 * 3600 * 1000);
alert( Dec31_1969 );

A string — parsed for you

Hand it a string and JavaScript parses it, using the same algorithm as Date.parse (covered at the end of this page).

let date = new Date("2019-08-15");
alert(date);
// The time is not set, so it's assumed to be midnight GMT and
// is adjusted according to the time zone the code runs in.
// So the result could be
// Thu Aug 15 2019 10:00:00 GMT+1000 (Australian Eastern Standard Time)
// or
// Wed Aug 14 2019 17:00:00 GMT-0700 (Pacific Daylight Time)

Notice the trap: "2019-08-15" with no time is read as midnight UTC, then displayed in the reader’s local zone. Someone in Los Angeles sees the previous evening. The stored instant is identical; only the local label differs.

Separate components

Pass the pieces individually and the date is built in the local time zone. Only the first two are required:

new Date(year, month, date, hours, minutes, seconds, ms)
  • year should have 4 digits. For backward compatibility 2 digits are accepted and read as 19xx (so 98 means 1998), but always write 4 — the shorthand is a legacy footgun.
  • month counts from 0 (January) up to 11 (December).
  • date is the day of the month; if you omit it, 1 is assumed.
  • hours, minutes, seconds, ms each default to 0 when left out.
year
required · 4 digits
month
required · 0–11
date
default 1
hours
default 0
minutes
default 0
seconds
default 0
ms
default 0
The seven positional arguments. Only year and month are required; the rest default to the first day / zero.
new Date(2022, 0, 1, 0, 0, 0, 0); // 1 Jan 2022, 00:00:00
new Date(2022, 0, 1); // the same, hours etc. are 0 by default

The finest resolution is 1 ms:

let date = new Date(2022, 0, 1, 2, 3, 4, 567);
alert( date ); // 1.01.2022, 02:03:04.567

Access date components

Once you have a Date, these methods read individual pieces out of it:

You can also ask for the day of the week:

  • getDay() — the weekday, 0 (Sunday) through 6 (Saturday). Sunday is always the first day here; some countries start their week on Monday, but that convention can’t be changed.
getMonth()
0 Jan
1 Feb
2 Mar
11 Dec
getDay()
0 Sun
1 Mon
2 Tue
6 Sat
Two zero-based scales that trip people up: getMonth() and getDay().

Every method above reports its answer in the local time zone.

Build a date below and watch the four readers report on it. Change the month and day to feel the two zero-based scales at once: getMonth() comes back one lower than the number you typed, and getDay() tells you the weekday.

interactiveReading components out of a Date

For each one there’s a UTC twin that reads the same date in UTC+0 instead: getUTCFullYear(), getUTCMonth(), getUTCDay(), and so on. Slot "UTC" in right after "get".

If your local zone is offset from UTC, these two lines disagree:

// current date
let date = new Date();

// the hour in your current time zone
alert( date.getHours() );

// the hour in UTC+0 time zone (London time without daylight savings)
alert( date.getUTCHours() );

Two more readers have no UTC form, because the values they return aren’t tied to any zone:

  • getTime() — the timestamp: milliseconds since 1 Jan 1970 UTC+0. This is the raw number the Date wraps.
  • getTimezoneOffset() — the gap between UTC and local time, in minutes.
// if you are in time zone UTC-1, outputs 60
// if you are in time zone UTC+3, outputs -180
alert( new Date().getTimezoneOffset() );

The sign feels backwards at first: zones ahead of UTC give a negative offset. Think of it as “minutes you’d add to local time to reach UTC.” UTC+3 is three hours ahead, so you subtract 180 minutes to get back to UTC.

Setting date components

A matching family of setters writes components back into a Date:

Each of these except setTime() has a UTC variant, such as setUTCHours().

Some setters take several components at once, like setHours(hour, min, sec, ms). Anything you leave out is untouched:

let today = new Date();

today.setHours(0);
alert(today); // still today, but the hour is changed to 0

today.setHours(0, 0, 0, 0);
alert(today); // still today, now 00:00:00 sharp

Autocorrection

Here’s where Date earns its keep. Set a component out of its normal range and the object quietly rolls over into a valid date.

let date = new Date(2014, 0, 32); // 32 Jan 2014 ?!?
alert(date); // ...is 1st Feb 2014!

January has 31 days, so “day 32” spills into the next month. Out-of-range pieces are redistributed automatically.

new Date(2014, 0, 32)
Jan has 31 days → 32 = 31 + 1 →
1 Feb 2014
Autocorrection: an impossible component overflows into the next unit up.

This turns tricky calendar math into plain addition. Say you want two days after “28 Feb 2024.” Depending on the leap year that’s “1 Mar” or “2 Mar” — but you don’t have to work it out. Just add:

let date = new Date(2024, 1, 28);
date.setDate(date.getDate() + 2);

alert( date ); // 1 Mar 2024 (2024 is a leap year, so Feb has 29 days)

The same trick handles “N units from now.” Here’s 70 seconds ahead, crossing the minute boundary without any effort on your part:

let date = new Date();
date.setSeconds(date.getSeconds() + 70);

alert( date ); // shows the correct date

Zero and negative values roll the other direction. Day 0 of a month lands on the last day of the previous month — a clean way to ask “how many days did last month have?”:

let date = new Date(2023, 0, 2); // 2 Jan 2023

date.setDate(1); // set day 1 of the month
alert( date );

date.setDate(0); // the minimum day is 1, so day 0 means the last day of the previous month
alert( date ); // 31 Dec 2022

Try it yourself. The demo starts from 28 Feb 2024 and adds however many days you ask for — positive numbers roll forward across month and year boundaries, negative numbers roll back, and you never touch the calendar math:

interactiveAdding days rolls the date over automatically

Date to number, date diff

Convert a Date to a number and you get its timestamp, exactly what date.getTime() returns:

let date = new Date();
alert(+date); // milliseconds count, same as date.getTime()

The unary + triggers numeric conversion. This has a genuinely useful consequence: you can subtract dates, and the result is their gap in milliseconds.

let start = new Date(); // start measuring time

// do the job
for (let i = 0; i < 100000; i++) {
  let doSomething = i * i * i;
}

let end = new Date(); // end measuring time

alert( `The loop took ${end - start} ms` );

When you write end - start, subtraction has no meaning for objects, so both dates convert to their timestamps and the numbers subtract. Comparisons like < and > work the same way.

end (Date)
→ +end →
1751692800120
start (Date)
→ +start →
1751692800108
difference =
12 ms
Subtracting two dates converts each to its millisecond timestamp, then subtracts the numbers.

Date.now()

If measuring elapsed time is all you’re after, you don’t need a full Date object at either end.

Date.now() returns the current timestamp directly. It’s semantically the same as new Date().getTime(), but it skips building the intermediate Date — so it’s faster and creates no object for the garbage collector to clean up.

That matters in tight loops, games, animation frames, and anywhere you’d otherwise create thousands of throwaway dates. Prefer it for timing:

let start = Date.now(); // milliseconds count from 1 Jan 1970

// do the job
for (let i = 0; i < 100000; i++) {
  let doSomething = i * i * i;
}

let end = Date.now(); // done

alert( `The loop took ${end - start} ms` ); // subtract numbers, not dates

Run it live. Pick a loop size, hit the button, and the elapsed time is just Date.now() at the end minus Date.now() at the start:

interactiveTiming a loop with Date.now()

Benchmarking

Want to know which of two functions is genuinely faster? Measuring it honestly is harder than it looks.

Take two functions that both compute the difference between two dates:

// we have date1 and date2 — which returns their difference in ms faster?
function diffSubtract(date1, date2) {
  return date2 - date1;
}

// or
function diffGetTime(date1, date2) {
  return date2.getTime() - date1.getTime();
}

They produce identical results. One leans on the date-to-number conversion; the other calls getTime() explicitly. Which wins?

Because each call is tiny, a single run tells you nothing — the numbers are lost in noise. Run each function many times, at least 100000, and time the whole batch:

function diffSubtract(date1, date2) {
  return date2 - date1;
}

function diffGetTime(date1, date2) {
  return date2.getTime() - date1.getTime();
}

function bench(f) {
  let date1 = new Date(0);
  let date2 = new Date();

  let start = Date.now();
  for (let i = 0; i < 100000; i++) f(date1, date2);
  return Date.now() - start;
}

alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' );
alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' );

getTime() comes out clearly faster, because it skips the object-to-number conversion, which engines find easier to optimize.

Good result — but not a trustworthy benchmark yet. Picture this: while bench(diffSubtract) runs, the CPU is busy with some other process and has fewer cycles to spare. By the time bench(diffGetTime) runs, that other work has finished and it gets the machine to itself. On a modern multi-process OS that’s an everyday occurrence.

The first function was handicapped by timing, not by its code. That skews the comparison.

To get a fair read, run the whole set of benchmarks many times over and alternate them.

naive: all of A, then all of B
A
A
A
B
B
B
background spike hits only A → A looks slow unfairly
interleaved: A, B, A, B, …
A
B
A
B
A
B
interference is shared → totals stay comparable
Naive vs. interleaved benchmarking. Alternating runs spreads any background interference evenly across both contestants.
function diffSubtract(date1, date2) {
  return date2 - date1;
}

function diffGetTime(date1, date2) {
  return date2.getTime() - date1.getTime();
}

function bench(f) {
  let date1 = new Date(0);
  let date2 = new Date();

  let start = Date.now();
  for (let i = 0; i < 100000; i++) f(date1, date2);
  return Date.now() - start;
}

let time1 = 0;
let time2 = 0;

// run bench(diffSubtract) and bench(diffGetTime) each 10 times, alternating
for (let i = 0; i < 10; i++) {
  time1 += bench(diffSubtract);
  time2 += bench(diffGetTime);
}

alert( 'Total time for diffSubtract: ' + time1 );
alert( 'Total time for diffGetTime: ' + time2 );

One more wrinkle. Modern engines only pour their heavy optimizations into “hot” code — functions that have already run many times. Cold first runs are slower and can muddy the numbers. So warm the functions up before you start counting:

// added for "heating up" prior to the main loop
bench(diffSubtract);
bench(diffGetTime);

// now benchmark
for (let i = 0; i < 10; i++) {
  time1 += bench(diffSubtract);
  time2 += bench(diffGetTime);
}

Date.parse from a string

Date.parse(str) reads a date out of a string and returns its timestamp.

The format it expects is YYYY-MM-DDTHH:mm:ss.sssZ:

  • YYYY-MM-DD is the date — year, month, day.
  • T separates the date from the time.
  • HH:mm:ss.sss is the time — hours, minutes, seconds, milliseconds.
  • The optional Z at the end gives the time zone as +-hh:mm. A bare Z means UTC+0.
2019-08-15
date
T
delim
14:22:30.500
time
-04:00
zone (opt.)
Anatomy of an ISO 8601 date string.

Shorter forms work too: YYYY-MM-DD, or YYYY-MM, or just YYYY.

Date.parse(str) returns the timestamp (milliseconds since 1 Jan 1970 UTC+0). If the string doesn’t match a format it understands, it returns NaN.

let ms = Date.parse('2019-08-15T14:22:30.500-04:00');

alert(ms); // 1565893350500  (timestamp)

Feed that number straight into new Date to get a real object back:

let date = new Date( Date.parse('2019-08-15T14:22:30.500-04:00') );

alert(date);

Edit the string below and watch the parse result. A valid ISO string yields a timestamp; anything the engine can’t read yields NaN. Try dropping the time, then try a non-ISO form like 15.08.2019 to see it fail:

interactiveDate.parse: valid ISO vs. NaN

Summary

  • Date and time in JavaScript live in a single Date object. There’s no separate “date only” or “time only” — a Date always carries both.
  • Months count from zero (January is 0).
  • Weekdays from getDay() also count from zero, and zero is Sunday.
  • A Date auto-corrects when you set an out-of-range component, which makes adding and subtracting days, months, and hours painless.
  • Subtracting two dates gives the gap in milliseconds, because a Date becomes its timestamp when converted to a number.
  • Use Date.now() to grab the current timestamp without allocating an object.

Remember that JavaScript timestamps are in milliseconds, not seconds — a frequent source of off-by-1000 bugs when talking to systems that use seconds.

Need finer resolution than a millisecond? Date can’t give it, but most environments can. In the browser, performance.now() returns milliseconds since the page began loading, with microsecond precision (3 digits after the decimal point):

alert(`Loading started ${performance.now()}ms ago`);
// Something like: "Loading started 34731.26000000001ms ago"
// .26 is microseconds (260 microseconds)
// digits past the first 3 after the point are floating-point noise

Node.js exposes process.hrtime.bigint() and other high-resolution options. Almost every platform can measure below a millisecond — it just isn’t part of Date.