Think that you can calculate date intervals without worrying about leap years? Think again. It’s true that, as long as you follow the guidelines set out in my previous Calculating the Difference between Two Dates article, the leap years will be accounted for and handled correctly. But this presumes that you aren’t interested in any particulars about the leap years themselves.
If you ever find yourself trying to calculate any kind of annualized figures, you’ll soon realize that it can pay off to know how to separate leap years from non-leap years. This article will introduce a few useful functions that you can use to work with leap years if and when the need arises.
And by the way, this is leading up to something pretty useful in the next article, where we will implement a JavaScript annualized returns calculator which will feature the HTML5 Date Picker element with a jQuery widget fallback.
Determining Whether or not a Given Year is a Leap Year
The first step in working with leap years is determining whether or not a given date contains a leap year. The following method is added to the Date’s prototype object so that it can be applied directly to any Date object as in aDate.isLeapYear(). It accepts a boolean that tells it whether to use Universal (UTC) time. While you wouldn’t think that a date would be affected by UTC, if the time is close enough for midnight for the time zone to shift the UTC into the next year, this method will return the correctly adjusted day. For more information on UTC time, see my Date Creation in JavaScript article.
Date.prototype.isLeapYear = function(utc) { var y = utc ? this.getUTCFullYear() : this.getFullYear(); return !(y % 4) && (y % 100) || !(y % 400); };
The above expression evaluates whether or not the given date falls within a leap year using the three following Gregorian calendar rules:
- Most years divisible by 4 are Leap Years (i.e. 1996)
- However, most years divisible by 100 are not (i.e. 1900)
- Unless they are also divisible by 400, in which case they are leap years (i.e. 2000)
Calculating Days while Ignoring Leap Years
Our second leap year function calculates the number of days between two dates while ignoring the extra leap year days. Such a function is useful when you want to divide the total number of days by 365 to arrive to a yearly figure.
The function would still have to take into account if the current year was a leap year and the two dates span the end of February. It does that by creating new dates using the non-leap year of 2010, which is used to determine the number of days difference within the same year. That figure is then added to 365 multiplied by number of years difference.
function daysBetweenExcludingLeapYears( date1, date2, utc ) { //2010 was NOT a leap year var cmpDate1 = new Date(2010, date1.getMonth(), date1.getDate()); var cmpDate2 = new Date(2010, date2.getMonth(), date2.getDate()); var date1Year = utc ? date1.getUTCFullYear() : date1.getFullYear(); var date2Year = utc ? date2.getUTCFullYear() : date2.getFullYear(); return Date.daysBetween(cmpDate1, cmpDate2) + (365 * (date1Year - date2Year)); }
Here is the static Date.daysBetween() function. It was covered in the Calculating the Difference between Two Dates article.
Date.daysBetween = function( date1, date2 ) { //Get 1 day in milliseconds var one_day=1000*60*60*24; // Convert both dates to milliseconds var date1_ms = date1.getTime(); var date2_ms = date2.getTime(); // Calculate the difference in milliseconds var difference_ms = date2_ms - date1_ms; // Convert back to days and return return Math.round(difference_ms/one_day); }
Counting Only Leap Year Days between Two Dates
Our final function of the day tallies up the number of days between two dates that are part of leap years. It can be useful in separating leap year days from non-leap year ones. It does that by breaking down the date range into three parts: the partial first year days, the full years, and the partial end year days. In all three parts, the isLeapYear() function is used to good effect in deciding whether or not to count the days of that year.
Again, the optional utc boolean argument is present. However, in this function it is used to set the year getter and setter to the correct function at the top of the function because the getter in particular is called numerous times. A working date is stored in a temporary (tmp) variable using the new Date(milliseconds) constructor so that a copy is created:
Date.leapYearDaysBetween = function(date1, date2, utc) { var count = 0; var fnGetYear = utc ? getUTCFullYear : getFullYear; var fnSetYear = utc ? setUTCFullYear : setFullYear; if (d1.fnGetYear() == date2.fnGetYear() && date1.isLeapYear()) { count = Date.daysBetween(date1,date2); } else { var lastDayInYear = new Date(date1.fnGetYear(), 11, 31); var tmp = new Date(date1.getTime()); //copy the date while (tmp.fnGetYear() < date2.fnGetYear()) { if (tmp.isLeapYear()) count += Date.daysBetween(tmp,lastDayInYear); tmp.fnSetYear(tmp.fnGetYear()+1); } if ( date2.isLeapYear() ) { var firstDayInYear = new Date(tmp.fnGetYear(), 0, 1); count += Date.daysBetween(firstDayInYear, date2); } } return count; }
In case you’re wondering, I did not include an equivalent function for calculating non-leap year days because that can be ascertained by subtracting the results of the Date.leapYearDaysBetween() function from those of Date.daysBetween().
Conclusion
As we’ll be seeing in the next article, leap years take on extra importance when dealing with annualized figures. In fact, you might be surprised just how much of a difference it makes for accuracy when you account for leap years properly.
About the Author
Rob Gravelle resides in Ottawa, Canada, and is the founder of GravelleConsulting.com. Rob has built systems for Intelligence-related organizations such as Canada Border Services, CSIS as well as for numerous commercial businesses. Email Rob to receive a free estimate on your software project. Should you hire Rob and his firm, you’ll receive 15% off for mentioning that you heard about it here!