Date setters (setMonth, setDate, setHours…)

Three surprises in one family: they change the object you called them on, they hand back a number instead of the object, and an out-of-range value rolls over instead of failing.

Date methodsES1 (1997)Live demo
Common call
d.setMonth(d.getMonth() + 1)
Returns
a timestamp — not the Date
Replaces
reconstructing a Date from parts
Watch out
MUTATES, and Feb 31 becomes March 3
date.setMonth(m), date.setDate(d), date.setFullYear(y)
→ number

Demo

Live evaluation
Try:
Inputs
isostringan ISO date
mnumbermonth to set (0–11)
Output
(d => { d.setUTCMonth(1); return d.toISOString(); })(new Date('2026-01-31T12:00:00Z'))
'2026-03-03T12:00:00.000Z'

The first case is the classic month-arithmetic bug. Setting the month of 31 January to February asks for 31 February, which does not exist — so it rolls forward three days to 3 March. Nothing warns you, and the result is a valid date in the wrong month. The third case shows the same mechanism upward: month 12 is out of the 0–11 range, so it becomes January of the following year. That rollover is occasionally useful and far more often the reason a recurring-date calculation drifts.

Parameters

NameTypeRequiredDescription
valuenumberyesThe new component value. Out-of-range values are not rejected — they roll forward or backward, so setMonth(12) advances the year and setDate(0) goes to the last day of the previous month.

Return value

number — The new timestamp in milliseconds — NOT the Date object. They mutate the Date in place, so chaining does not work.

Common patterns

Add days safely
Rollover works in your favour here.
const d = new Date(start);
d.setDate(d.getDate() + 7);
Add months safely
Clamp to the end of the target month.
const d = new Date(start);
const day = d.getDate();
d.setDate(1);
d.setMonth(d.getMonth() + 1);
d.setDate(Math.min(day, daysInMonth(d)));
Do not chain
The return value is a number.
const d = new Date();
d.setHours(0);
d.setMinutes(0);   // separate statements

Examples

1. Feb 31 rolls to March
const d = new Date(Date.UTC(2026, 0, 31)); d.setUTCMonth(1); d.toISOString().slice(0, 10)
Returns
'2026-03-03'
2. Returns a number
typeof new Date().setMonth(1)
Returns
'number'
3. It mutates
const d = new Date('2026-03-15T12:00:00Z'); d.setUTCMonth(5); d.toISOString()
Returns
'2026-06-15T12:00:00.000Z'
4. setDate(0) goes back
const d = new Date(Date.UTC(2026, 3, 15)); d.setUTCDate(0); d.toISOString().slice(0, 10)
Returns
'2026-03-31'
5. Month 12 is next January
new Date(2026, 12, 1).getFullYear()
Returns
2027
6. setTime replaces everything
const d = new Date(); d.setTime(0); d.toISOString()
Returns
'1970-01-01T00:00:00.000Z'

Pitfalls

1. Month arithmetic silently rolls over
Adding a month to the 31st of a short-followed month lands in the month after next. Any "same day next month" calculation on month-end dates drifts, and the bug only appears for a few days each month — so it survives testing.
Skips February
const d = new Date(Date.UTC(2026, 0, 31));
d.setUTCMonth(d.getUTCMonth() + 1);
d.toISOString().slice(0, 10)
'2026-03-03'
Go via day 1
const d = new Date(Date.UTC(2026, 0, 31));
d.setUTCDate(1);
d.setUTCMonth(1);
d.toISOString().slice(0, 10)
'2026-02-01'
2. They return a timestamp, not the Date
So chaining fails — the second call is made on a number, which has no setMinutes. The error message about a number not having the method is the clue, and it appears one line after the actual mistake.
Cannot chain
new Date().setHours(0).setMinutes(0)
TypeError: ...setMinutes is not a function
Separate statements
const d = new Date();
d.setHours(0);
d.setMinutes(0);
works
3. They mutate a shared Date
A Date passed into a function and modified there changes for the caller too. Dates are objects, and there is no non-mutating setter — copy with new Date(d) before adjusting anything you did not create.
Caller affected
function startOfDay(d) { d.setHours(0, 0, 0, 0); return d; }
the argument is changed
Copy first
function startOfDay(d) { const c = new Date(d); c.setHours(0, 0, 0, 0); return c; }
the argument is safe
4. setFullYear on 29 February
Moving a leap day to a non-leap year rolls it to 1 March. Birthday and anniversary logic hits this once every four years, which is exactly the interval at which nobody is watching.
Rolls to March
const d = new Date(Date.UTC(2024, 1, 29));
d.setUTCFullYear(2026);
d.toISOString().slice(0, 10)
'2026-03-01'
Clamp the day
const d = new Date(Date.UTC(2026, 1, Math.min(29, 28)));
'2026-02-28'

When to use

Use it
  • Adding or subtracting days, where rollover is what you want
  • Zeroing out the time part to get the start of a day
  • setTime to jump to an absolute timestamp
Reach for something else
  • Month arithmetic on month-end dates → clamp explicitly, or use a library
  • Chaining → each call returns a number
  • A Date you did not create → copy it first
  • Anything a library like Temporal or date-fns does immutably

Notes

Complexity
O(1) each
Return
The new timestamp as a number; the Date is mutated in place
CPython impl
V8: Builtins-date setters
Memory
No allocation
Thread-safe
Single-threaded; the Date is mutated, so shared references see the change

FAQ

Set the day to 1 first, change the month, then set the day to the smaller of the original day and the new month length. Without the clamp, month-end dates skip a month. This is the single best argument for a date library.

function addMonth(d) {
  const day = d.getDate();
  const c = new Date(d);
  c.setDate(1);
  c.setMonth(c.getMonth() + 1);
  const last = new Date(c.getFullYear(), c.getMonth() + 1, 0).getDate();
  c.setDate(Math.min(day, last));
  return c;
}

History

ES1
The full setter family present from the first version, mutating and returning a timestamp.
ES5
setYear relegated to Annex B alongside getYear.