Menu

JavaScript Date and Time: new Date, Formatting, and Comparing

How the JavaScript Date object really works — creating dates, formatting them, doing arithmetic, handling timezones, and the quirks that trip people up.

The Date Object Is a Moment in Time

A Date in JavaScript represents a single instant — internally, just a number of milliseconds since January 1, 1970 UTC (the "Unix epoch"). Everything else — years, months, days, timezones, formatting — is a view layered on top of that number.

index.js
Output
Click Run to see the output here.

now.getTime() is the raw millisecond count. Everything a Date does — comparing, adding days, formatting — boils down to manipulating that number and then reinterpreting it.

Keep that model in your head. A Date isn't "March 14 in Paris." It's a universal instant that can be displayed as March 14 in Paris, or March 13 in Los Angeles, depending on the timezone you look at it through.

Creating Dates

There are four main ways to construct a Date:

index.js
Output
Click Run to see the output here.

Two things to notice:

  • The parts constructor uses zero-indexed months. 2 means March. January is 0. This is a constant source of off-by-one bugs — everywhere else in the API, months are 0-indexed too, so at least it's consistent with itself.
  • new Date("2026-03-14") (without a time) is parsed as UTC midnight. new Date("2026-03-14T09:30") (without a Z) is parsed as local time. This asymmetry is a classic gotcha.

For "right now" as a number, prefer Date.now() — it skips the object allocation:

index.js
Output
Click Run to see the output here.

Date.now() is the right tool for measuring elapsed time, timeouts, and anything where you don't need calendar arithmetic.

Reading Parts of a Date

Once you have a Date, you pull components out with getters. There are two flavors of each: local-time and UTC.

index.js
Output
Click Run to see the output here.

The local ones depend on whoever's machine is running the code. If you're storing or comparing dates across users and servers, pick UTC explicitly or you'll chase phantom bugs. Rule of thumb: use UTC getters for anything going into a database or a log; use local getters for anything you're about to show a human.

Don't use getYear(). It's a legacy method that returns year - 1900 and exists only for compatibility. Always reach for getFullYear().

Formatting for Humans

Avoid date.toString() for anything you care about — its output is locale- and engine-dependent. There are two formatters worth knowing.

For a standard machine-readable string, use toISOString():

index.js
Output
Click Run to see the output here.

That's the format to use when logging, storing in JSON, or sending over the network. It's always UTC, always unambiguous.

For a human-facing string, use Intl.DateTimeFormat or the toLocale* methods, which wrap it:

index.js
Output
Click Run to see the output here.

Intl.DateTimeFormat handles locales, timezones, and every combination of fields you'd want. Reach for it before writing manual ${year}-${month}-${day} formatting — that kind of string building is where off-by-one month bugs live.

Comparing Dates

Two Date objects that represent the same instant are not === equal — === checks object identity, not value. Compare their timestamps instead:

index.js
Output
Click Run to see the output here.

For ordering, the comparison operators work directly because they coerce to numbers:

index.js
Output
Click Run to see the output here.

Subtraction gives you the gap in milliseconds. Divide by 1000 * 60 * 60 * 24 for days. Write that constant out the first time; after a while you'll recognize 86_400_000 on sight.

Date Arithmetic

There's no addDays method. The idiomatic way is to use setDate, setMonth, etc. — they accept out-of-range values and roll over correctly:

index.js
Output
Click Run to see the output here.

Two things worth calling out:

  • new Date(date) copies the date. setDate mutates, so always copy first or you'll modify the caller's value.
  • setDate(35) on a 31-day month rolls into the next month automatically. Same for setMonth(14) — it advances the year. This makes arithmetic far less painful than it looks.

For anything complex — business days, recurring events, durations with month awareness — reach for a library (date-fns, Luxon, or the upcoming Temporal API). Rolling your own calendar math past "add a few days" is a tar pit.

Timezone Reality Check

Timezones are the single biggest source of date bugs. The rules worth internalizing:

  • A Date stores a UTC instant. The timezone is applied only when you read parts out or format it.
  • The timezone used by getHours(), getDate(), etc. is the local timezone of the machine running the code. Servers and browsers often disagree.
  • new Date("2026-03-14") (date only) parses as UTC. new Date("2026-03-14T00:00") (with time, no zone) parses as local. new Date(2026, 2, 14) (parts) is local.
index.js
Output
Click Run to see the output here.

When you need a specific timezone for display, pass timeZone to Intl.DateTimeFormat:

index.js
Output
Click Run to see the output here.

Same instant, two views. The Date object itself hasn't changed.

A Small Working Example

Putting it together — a function that formats how long ago something happened:

index.js
Output
Click Run to see the output here.

Timestamps in, human-readable string out. This is the shape of 90% of real-world date code: subtract two instants, divide by a unit, round, format.

What You Take Away

  • A Date is a UTC instant. Timezones appear when you read or format it.
  • Use Date.now() for timestamps, new Date() for calendar work.
  • Use toISOString() for storage and logs, Intl.DateTimeFormat for users.
  • Compare with getTime() or </>. Never ===.
  • Months are 0-indexed. Watch the date-only string parsing trap.
  • For serious date math, use a library.

Next: URLs and Query Strings

Dates often show up in URLs — filter by date range, pass a timestamp as a query parameter. Parsing and building URLs by hand is just as bug-prone as formatting dates by hand, and the standard library has a URL object that handles it cleanly. That's next.

Frequently Asked Questions

How do you get the current date in JavaScript?

Call new Date() with no arguments. It returns a Date object representing the moment the constructor ran. If you only want a numeric timestamp (milliseconds since 1970), use Date.now() — it's faster and doesn't allocate a full object.

How do you compare two dates in JavaScript?

Compare their timestamps, not the Date objects themselves. a.getTime() < b.getTime() works, and so does a < b because < coerces dates to numbers. But a === b does not — === checks object identity, so two Date objects representing the same instant are never strictly equal.

How do you format a date in JavaScript?

For anything user-facing, use Intl.DateTimeFormat or date.toLocaleDateString() — they handle locales and timezones properly. For machine-readable output, date.toISOString() gives you a standard string like 2026-03-14T09:30:00.000Z. Avoid date.toString() for storage; the format is locale-dependent.

Why is my JavaScript date off by one day?

Usually a timezone issue. new Date('2026-03-14') parses as UTC midnight, but date.getDate() returns the day in the local timezone — which can be the day before. Use getUTCDate() for the UTC day, or construct dates with new Date(year, month, day) which uses local time from the start.

Learn to code with Coddy

GET STARTED