ECMAScript Date

In ECMAScript, the Date object is the built-in mechanism for handling dates and times. At its core, a Date instance does not store a complex set of calendar fields; instead, it maintains a single numeric value: the time value. This value represents the number of milliseconds that have elapsed since the Unix Epoch—1970-01-01T00:00:00Z.

This internal timestamp can be positive or negative, allowing the Date object to represent dates far into the past or future (approximately $\pm 100,000,000$ days relative to the epoch). If a date is created with a value outside this range or if a string fails to parse, the instance becomes an Invalid Date, and its internal time value is set to NaN.

An important distinction in usage is that Date can be invoked both as a constructor and as a regular function. When called with new Date(), it returns a Date object. When called as a function—Date()—it returns a human-readable string representing the current time, regardless of any arguments passed to it.

Creating Date Instances

There are several ways to instantiate a Date object, depending on the available data:

// 1. Current time and date
const now = new Date(); 

// 2. From a millisecond timestamp
const fromTimestamp = new Date(1705307400000); 

// 3. From an ISO 8601 string
const fromString = new Date("2024-01-15T08:30:00.000Z"); 

// 4. From date components (Local Time)
// Note: Months are 0-indexed (0 = January, 11 = December)
const fromParts = new Date(2024, 0, 15, 8, 30, 0, 0); 

// 5. From date components (UTC Time)
const fromUtcParts = new Date(Date.UTC(2024, 0, 15, 8, 30, 0, 0));

Critical Implementation Details

When working with the Date constructor, developers should be aware of a few specific behaviors:

  • Zero-Indexed Months: The monthIndex argument starts at 0. Passing 0 results in January, while 11 results in December.
  • Two-Digit Years: If the year provided is between 0 and 99, JavaScript automatically maps it to the range 1900–1999. To specify a year like 0024 AD, you must use setFullYear().
  • Local vs. UTC: When using the (year, month, ...) signature, the inputs are interpreted according to the user's local system timezone.

Timestamps and UTC Methods

For many applications, working with raw timestamps is more efficient than manipulating date objects. Date.now() is a static method that returns the current time in milliseconds without the overhead of creating a new object.

Similarly, Date.UTC() allows you to calculate a timestamp based on UTC components without instantiating a Date object immediately.

Date.now(); // e.g., 1705307400000
Date.UTC(2024, 0, 15, 8, 30); // 1705307400000

const d = new Date(Date.UTC(2024, 0, 15, 8, 30));
console.log(d.getTime()); // 1705307400000
console.log(d.valueOf()); // 1705307400000 (identical to getTime())

It is vital to remember that Date instances do not store timezone information. They store a UTC timestamp. The timezone is only applied when you use "local" methods to read or write the date.

Reading and Modifying Date Components

The Date API provides two parallel sets of methods: one for the local timezone and one for UTC. Because Date objects are mutable, setter methods modify the original instance.

Getters and Setters

Common methods include:

  • Year: getFullYear() / setFullYear() $\leftrightarrow$ getUTCFullYear() / setUTCFullYear()
  • Month: getMonth() / setMonth() $\leftrightarrow$ getUTCMonth() / setUTCMonth()
  • Date: getDate() / setDate() $\leftrightarrow$ getUTCDate() / setUTCDate()
  • Day of Week: getDay() / getUTCDay() $\leftrightarrow$ getUTCDay() (No setter for day of week)
  • Time: getHours(), getMinutes(), getSeconds(), getMilliseconds() (and their UTC counterparts).

Automatic Rollover

One powerful (and sometimes surprising) feature of Date is automatic rollover. If you set a component to a value outside its normal range, JavaScript adjusts the other components accordingly.

const d = new Date(2024, 0, 31); // January 31, 2024
d.setMonth(1); // Set to February. 
// Since Feb 31 doesn't exist, it rolls over to March 2nd or 3rd.

Similarly, setting the date to 0 will move the object back to the last day of the previous month.

Timezone Offsets

To determine the difference between the local time and UTC, use getTimezoneOffset(). This returns the difference in minutes. For example, in a UTC+8 timezone, this method typically returns -480.

Parsing and Formatting

Parsing Strings

Date.parse() converts a date string into a millisecond timestamp. While many formats are supported across different browsers, the ISO 8601 format is the only one guaranteed by the ECMAScript specification to be parsed consistently.

Important Parsing Nuance:

  • A date-only ISO string (e.g., "2024-01-01") is treated as UTC.
  • A date-time string without a specified timezone (e.g., "2024-01-01T00:00:00") is treated as local time.

Formatting Output

Depending on the required output, Date provides several formatting methods:

  • toISOString(): Returns a string in the simplified ISO 8601 format (always UTC).
  • toJSON(): Used by JSON.stringify(); typically returns the same result as toISOString().
  • toString(): Returns a full string representation in the local timezone.
  • toLocaleString(), toLocaleDateString(), and toLocaleTimeString(): Returns strings formatted according to the user's locale and language settings.
const d = new Date("2024-01-01");
console.log(d.toISOString()); // "2024-01-01T00:00:00.000Z"
console.log(d.toString());    // Localized string (e.g., "Wed Jan 01 2024 ...")