setFullYear()
ES1+Sets the year of a Date object according to local time, optionally setting the month and day in the same call, and returns the new timestamp in milliseconds. It mutates the Date in place, and out-of-range month or day values are normalized by rolling into adjacent months or years.
Syntax
date.setFullYear(year, month, day)Parameters
year number An integer specifying the year
month number optionalAn integer between 0 and 11 representing the month
day number optionalAn integer between 1 and 31 representing the day of the month
Return Value
The number of milliseconds between the date and UNIX epoch
Examples
const date = new Date('2024-01-15');
date.setFullYear(2025);
console.log(date.getFullYear());
date.setFullYear(2026, 5, 20);
console.log(date.toLocaleDateString('ko-KR')); 📌 When to Use
Use setFullYear() when you need to move an existing Date to another year: building "same date last year" comparisons for analytics, year navigation in date pickers and calendars, computing anniversaries and renewal dates, or normalizing a recurring event template onto the current year. Its three-argument form, setFullYear(year, month, day), sets the whole calendar date atomically, which is safer than chaining setMonth() and setDate() because intermediate states cannot land on a nonexistent date (setting February on a Date currently at the 31st, for example, would roll over before your day assignment runs). The method mutates the receiver and returns the numeric timestamp rather than the Date, so it cannot be chained fluently and it will silently rewrite any Date instance shared across components; clone first (new Date(d)) whenever the original must survive. Normalization is a feature to use deliberately: Feb 29 moved to a non-leap year becomes March 1, so anniversary logic that means "Feb 28 in ordinary years" must clamp explicitly. For UTC-based pipelines use setUTCFullYear(), and in new code doing substantial calendar arithmetic, prefer the immutable Temporal API, whose with() and add() methods return new values and take an explicit overflow policy instead of silent rollover.
⚠️ Common Mistakes
Forgetting the mutation: passing a Date into a function that calls setFullYear() changes the caller's object too, since Dates are passed by reference. State managers and memoized components then observe values changing without any assignment, a notoriously confusing bug; clone before mutating.
Using the return value as if it were the Date: const next = date.setFullYear(y + 1) stores a number (the new timestamp), and next.getMonth later throws. The mutated original is the Date; the return exists mainly for legacy reasons.
The leap-day rollover: taking Feb 29, 2024 to 2025 yields March 1, 2025. Whether an anniversary, license expiry, or birthday should become Feb 28 or Mar 1 is a product decision, and setFullYear() silently picks rollover; clamp manually when policy says otherwise.
Changing year and month-day separately when both must change: date.setFullYear(2025) followed by setMonth/setDate can transit through invalid intermediate dates and normalize unexpectedly; the three-argument setFullYear(2025, 1, 28) avoids the intermediate state entirely.
Expecting timezone stability across the change: setFullYear() preserves the local wall-clock time, but the UTC offset for that wall time may differ in the target year (DST rules and even zone definitions change), so the instant shifts by more than a whole number of years. For instant-precise math, work in UTC.
Assuming validation: setFullYear(NaN) or absurd years do not throw, they produce an Invalid Date or a technically valid but nonsensical value that only fails later, often at toISOString(). Range-check inputs at the point of entry.
✅ Best Practices
Adopt clone-then-mutate as a reflex: const next = new Date(date); next.setFullYear(next.getFullYear() + 1). Treat any function mutating its Date argument as a bug unless mutation is its documented purpose.
Use the multi-argument form for compound changes, setFullYear(year, month, day) is atomic with a single normalization at the end, eliminating intermediate-state rollover bugs that plague chained setters.
Handle Feb 29 explicitly in year arithmetic: check isLeapYear(targetYear) (or compare the month after the move) and clamp to Feb 28 when your domain requires it; document the choice, since both behaviors are defensible.
Keep a project-level addYears(date, n) utility so cloning, clamping, and validation live in one tested function instead of being re-derived, slightly differently, at every call site.
For new or refactored code, reach for immutable alternatives: Temporal.PlainDate.prototype.with({ year }) returns a new date and accepts { overflow: "constrain" | "reject" } to make the Feb 29 policy explicit; date-fns addYears offers similar semantics for Date-based codebases.
⚡ Performance Notes
setFullYear() recomputes the internal timestamp from the adjusted calendar fields, a constant-time operation involving one local-time normalization; it is marginally heavier than a getter but still nanosecond-scale, and mutating an existing Date is cheaper than constructing a replacement, which is why tight loops that walk years (or the business-day loops that walk days) benefit from one mutable cursor object rather than per-iteration allocation. The clone-then-mutate pattern costs one small allocation per logical operation, negligible in UI code and the right default for correctness; only inside data-processing loops over many thousands of items is it worth reusing a scratch Date. Note that each set operation consults time zone data to renormalize, so batching field changes through the three-argument form does one normalization instead of three, a micro-optimization that also happens to be the correctness-preserving choice.
🌍 Real World Example
Year Navigation Component
Year navigation, the arrows on a date picker or the year-over-year toggle on a report, needs to move a reference date across years without corrupting shared state or landing on invalid dates. This example clones the current date, applies setFullYear(), and clamps the day for the Feb 29 case, returning a fresh object each time in the immutable style UI frameworks expect. The same pattern underlies anniversary reminders, fiscal-year rollovers, and comparison charts that align this year's series against last year's.
function createYearNavigator(initialDate) {
let currentDate = new Date(initialDate);
return {
current: () => new Date(currentDate),
goToYear: (year) => {
const newDate = new Date(currentDate);
newDate.setFullYear(year);
currentDate = newDate;
return currentDate;
},
nextYear: () => {
const newDate = new Date(currentDate);
newDate.setFullYear(newDate.getFullYear() + 1);
currentDate = newDate;
return currentDate;
},
prevYear: () => {
const newDate = new Date(currentDate);
newDate.setFullYear(newDate.getFullYear() - 1);
currentDate = newDate;
return currentDate;
}
};
}
const navigator = createYearNavigator('2024-06-15');
console.log(navigator.nextYear().getFullYear()); // 2025
console.log(navigator.goToYear(2020).getFullYear()); // 2020