JavaScript Date Calculations for Months

By Robert Gravelle

12/19/12

Under the best of circumstances, calculations involving dates can be an troublesome to nail down. Throw in month calculations, and you're bound to feel some pain. As we'll see here today, month durations are not as fixed as one might initially presume. It really depends on the context. JavaScript doesn't offer much in this department, so we have to come up with our own solutions. After some experimentation and scouring the Web, I have found some useful functions for most common calculations of month intervals. Enjoy!

"One Month" Defined

Unlike other date and time components such as years, weeks, hours, minutes, and seconds, months have the dubious distinction of being of variable length. Some have 30 days, others 31, and then there's February. Ah yes, good ole February, that contains the leap year day. Moreover, a period of one month can mean different things to different people. The first Monday of each month can be considered a monthly interval, as can the 20th to the 20th of the following month. Finally, the last day of the month to the last day of the following month is also sometimes interpreted as one month - even when the second month is shorter. As you can well imagine, given a starting date, calculating each of these intervals requires its own unique approach. In today's article, we'll examine a couple.

Get Your Interval Straight

The first step is to make sure that you are absolutely clear on what the monthly interval means in your particular circumstance. There's no programming knowledge required here, just reading the fine print or getting some verbal or written clarification. As an example, let's take a one month train travel pass. In the case of Europe's Eurorail and InterRail, a month encompasses the start day until midnight of the same numbered day of the following month. For instance, a ticket that started the 5th of May (00:01h midnight), would be valid until the 4th of June (23:59h midnight). In your case that a pass is purchased on the last day of the month, such as January 31st, it would remain valid until midnight of March 2nd (23:59h midnight). That's because there is no 31st of February. Therefore we have to add 3 days to 28 or two from the 29th to equal a full month of 31 days. It's only fair, since any other day such as the 24th to the 24th is equal to the full 31 days.

The Rollover Effect

There's one important thing that you should be aware of regarding dates in JavaScript. When you provide a value that it outside of that date part's range, it simply rolls over into a valid one. For instance, setting the month to minus one does not throw an error, but instead backs the date up one whole month from January:

  alert(new Date(2012, -1, 31)); //displays Sat Dec 31, 2011

Same goes for days:

  alert(new Date(2012, 1, 38)); //displays Fri Mar 9, 2012

It even works for years:

  alert(new Date(-3, 1, 30)); //displays Sun Mar 2, 4 B.C. !!!

Calculating One Full Month

We can count on the rollover effect to automatically adjust for the number of days in a month. Hence, a shorter second month will padded with the missing days just as we saw above:

  function calcFullMonth(startDate) {
  //copy the date
  var dt = new Date(startDate);
  dt.setMonth(dt.getMonth() + 1);
  return dt;
}

alert(calcFullMonth(new Date(2012, 0, 24)));  //displays Fri Feb 24, 2012
alert(calcFullMonth(new Date(2012, 0, 31)));  //displays Fri, Mar 2, 2012

Notice that because we are adding a month to the start date, we must copy it if we don't want to change the original date. That is easily done using the Date constructor that accepts the number of milliseconds. Since Date objects are already internally stored as milliseconds, no conversion is required.

With both dates above, the one month interval falls on the same day of the weeks (Friday), which is exactly what we want.

Calculating Full Months Offset

Rather than adding one month, it's better to create a function that can accept any positive or negative offset. We can also add the function to the Date object's prototype so that we can apply it directly to a given date:

Date.prototype.calcFullMonths = function(monthOffset) {
  //copy the date
  var dt = new Date(this);
  dt.setMonth(dt.getMonth() + monthOffset);
  return dt;
};

alert((new Date(2012, 0, 24)).calcFullMonths(3));   //displays Tue, Apr 24, 2012
alert((new Date(2012, 0, 31)).calcFullMonths(-6));  //displays Sun, Jul 31, 2011

Adding Months Without Rolling Over

There are times that you may not want a rollover to occur and simply select the last day of the following month, such as Jan 31st to Feb 28th. The trick there is to test that the rollover day is smaller than the starting one. If it is, we need to rollback to the last day of the skipped month. To do that, we set the day to zero (0), which works in every browser like any other rollover:

var dt = new Date(2008, 1, 0);
alert(dt); // displays the last day in January

IE 6: Thu Jan 31 00:00:00 EST 2008
IE 7: Thu Jan 31 00:00:00 EST 2008
IE 8: Beta 2: Thu Jan 31 00:00:00 EST 2008
Opera 8.54: Thu, 31 Jan 2008 00:00:00 GET-0600
Opera 9.27: Thu, 31 Jan 2008 00:00:00 GET-0600
Opera 9.60: Thu Jan 31 2008 00:00:00 GET-0600
Firefox 2.0.0.17: Thu Jan 31 2008 00:00:00 GET-0600 (Canada Eastern Standard Time)
Firefox 3.0.3: Thu Jan 31 2008 00:00:00 GET-0600 (Canada Eastern Standard Time)
Google Chrome 0.2.149.30: Thu Jan 31 2008 00:00:00 GET-0600 (Canada Eastern Standard Time)
Safari for Windows 3.1.2: Thu Jan 31 2008 00:00:00 GET-0600 (Canada Eastern Standard Time)

Here's the code:

Date.prototype.calcMonthsNoRollover = function(monthOffset){
  var dt = new Date(this);
  dt.setMonth(dt.getMonth()+ monthOffset) ;
  if (dt.getDate() < this.getDate()) { dt.setDate(0); }
  return dt;
};

alert(new Date(2012, 0, 24).addMonths(1));  //displays Fri, Feb 24, 2012
alert(new Date(2012, 0, 31).addMonths(3));  //displays Mon, Apr 30, 2012

Conclusion

I hope that you found today's article useful. Date calculations involving months need not be difficult, as long as you're clear on your month interval definition and know which JavaScript behavior to apply to it.



Make a Comment

Loading Comments...

  • Web Development Newsletter Signup

    Invalid email
    You have successfuly registered to our newsletter.
  •  
  •  
  •