Using The Joyous Power Of Relative Dates To Calculate Days-In-Month In JavaScript
The Date object in JavaScript is kind of an amazing entity. For the most part, I rarely do anything more than "new" it into existence; however, when I have to manipulate dates, I am brought much joy by how well the JavaScript Date object handles relative dates. As an example of this power, I wanted to demonstrate how easy it is to determine the number of days in a given month while taking leap years into account.
When creating or modifying a Date object in JavaScript, you can supply it with individual year, month, and day (date) values (as well as hours, minutes, etc.). If these values represent "in-boundary" values, the Date object will be updated as expected. However, if these values represent "out-of-boundary" values, the Date object will work by rolling the date forward or backward based on the relative magnitude of the given value.
For example, if you take a Date object and set the month value to be 17, it doesn't throw an out-of-bounds error (months go from 0-11). Instead, it rolls the date forward by 17-months. As such, the following two instantiations result in the same epoch time:
new Date( 2017, 17, 1 ) ~= new Date( 2018, 5, 1 )
This kind of relative date math also works when you're modifying existing Date values. For example, calling:
date.setMonth( 17 )
... is the same as calling:
date.setYear( date.getFullYear() + 1 )
date.setMonth( 5 )
This might seem strange at first. But, when you start using the Date entity, this becomes a magical feature (assuming you can remember which date parts start at 1 and which start at zero).
In my Incident Commander Angular application, I created a Date/Time component that separated-out all of the date parameters into individual Select menus. When the user changed the Month or Year values, I had to update the available options in the Day dropdown. This way, the user couldn't select 31 in April, for example. Or, 29 in February - unless, of course, it was a leap year.
To calculate the Day value - or rather, the "days in month" for the selected month/year combination - I created a Date object using the following settings:
- Year: selected_year
- Month: ( selected_month + 1 )
- Date: 0
Because the Date object can be created with "out-of-boundary" values, (month+1) is always safe. If month is "March", (month+1) is "April". If month is "December", (month+1) is "January".
In a Date object, the date (day) values range from 1-31. So, if we create a new Date object with a "zero" date, it's essentially a -1 relative day value. So, the above Date arguments say "Roll forward to the following month, then roll-back one day". This should leave us with a Date object that represents the last day of the selected month. And, if we inspect that Date, it should tell us how many days are in the selected month.
To see this in action, let's calculate the days in each month for 2016 (a leap year) and 2017 in Node.js:
// I return the number of days in the given month. The year is required because the
// value may change in leap years.
function daysInMonth( month, year ) {
var lastDayOfMonth = new Date(
year,
// Go to the "next" month. This is always safe to do; if the next month is beyond
// the boundary of the current year, it will automatically become the appropriate
// month of the following year. For example, ( December + 1 ) ==> January.
( month + 1 ),
// Go to the "zero" day. Since days range from 1-31, the "0" day will
// automatically roll back to the last day of the previous month. And, since we
// did ( month + 1 ) above, it will be ( month + 1 - 1 ) ... or simply, the last
// day of "month".
0
);
return( lastDayOfMonth.getDate() );
}
// I return the name (abbreviation) of the given month index.
function monthAsString( month ) {
var months = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
return( months[ month % 12 ] );
}
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
// Let's test the months in 2016, since we know it's a leap year, and 2017, since we
// know it is not a leap year. And, since the Date object is a beast with relative
// dates, we can just loop over a 24-month period and the latter half of the loop will
// automatically roll over to the next year.
for ( var i = 0 ; i < 24 ; i++ ) {
console.log( `Days[ ${ monthAsString( i ) } ]: ${ daysInMonth( i, 2016 ) }` );
// To make the console-logging easier to read, split 2016 and 2017.
( i === 11 ) && console.log( "----" );
}
As you can see, I'm using the (month+1) and zero date combination I discussed above. But, I'm also using 2016 for all calculations while I loop over the months using 0-23; and, I'm passing those non-modded month values into my function. This is just to demonstrate that those months will be relative to 2016, which will result in 2017 Dates in the latter half of the loop. And, when we run this code in Node.js, we get the following terminal output:
As you can see, we successfully determined that February has 29 days in 2016, which is a leap year, and 28 days in 2017, which is a normal year.
The Date object in JavaScript is super powerful. It can be a bit tricky to work with because the days start at 1, the months start at 0, and the years are absolute (and all time values start at zero). But, once you commit that to memory, the Date object yields a lot of functionality. And, being able to use relative, out-of-bound values when creating or modifying a Date object is just fantastic.
Want to use code from this post? Check out the license.
Reader Comments