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.
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)
yearshould have 4 digits. For backward compatibility 2 digits are accepted and read as19xx(so98means1998), but always write 4 — the shorthand is a legacy footgun.monthcounts from0(January) up to11(December).dateis the day of the month; if you omit it,1is assumed.hours,minutes,seconds,mseach default to0when left out.
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:
- getFullYear() — the year, all 4 digits.
- getMonth() — the month, 0 to 11.
- getDate() — the day of the month, 1 to 31. The name reads oddly, but that’s what it returns.
- getHours(), getMinutes(), getSeconds(), getMilliseconds() — the matching time pieces.
You can also ask for the day of the week:
- getDay() — the weekday,
0(Sunday) through6(Saturday). Sunday is always the first day here; some countries start their week on Monday, but that convention can’t be changed.
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.
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
Datewraps. - 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:
- setFullYear(year, [month], [date])
- setMonth(month, [date])
- setDate(date)
- setHours(hour, [min], [sec], [ms])
- setMinutes(min, [sec], [ms])
- setSeconds(sec, [ms])
- setMilliseconds(ms)
- setTime(milliseconds) — rewrites the whole date from a timestamp (ms since 01.01.1970 UTC).
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.
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:
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.
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:
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.
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-DDis the date — year, month, day.Tseparates the date from the time.HH:mm:ss.sssis the time — hours, minutes, seconds, milliseconds.- The optional
Zat the end gives the time zone as+-hh:mm. A bareZmeans UTC+0.
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:
Summary
- Date and time in JavaScript live in a single Date object. There’s no separate “date only” or “time only” — a
Datealways carries both. - Months count from zero (January is
0). - Weekdays from
getDay()also count from zero, and zero is Sunday. - A
Dateauto-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
Datebecomes 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.