Because the definition of "age in months" is... flexible, the easiest way is to use a little arithmetic as you would compute it in your head, and not involve the Date
class.
For the [a] human interpretation of "age in months", the rule is
Compute the difference between the two dates in months,
as if the day-of-the-month was the 1st of the month for both dates
Subtract 1 to exclude the final month
Then, if the day-of-the-month of the last day of the period is on
or after the day-of-the-month of the first day of the period, the [potentially partial] final month is complete: add 1 to restore the count
The one fly in the ointment is, since months contain different numbers of days, dealing with the cases where the 2 months differ in their number of days.
If, however, the end month is shorter than the start month, you can get into a situation where the boundary condition can never be met (e.g., the start date is the February 28th and the end date is March 31st. To fix that, you need to look at the "end of the month" as being a window ranging from the last day of the start month through the last day of the end month inclusive.
That leads to this code. I'm using a structure like the following to represent a date:
{
year: 2021 , // 4-digit year
month: 11 , // month of year (1-12 mapping to January-December)
day: 23 // day of month (1-[28-31] depending on year/month
}
Ensuring that the data in that struct represents a valid date is left as an exercise for the reader.
The code is not that complicated:
/**
*
* @param {object} bgn - start date of period
* @param {number} bgn.year - 4-digit year
* @param {number} bgn.month - month of year [1-12]
* @param {number} bgn.day - day of month [1-31]
*
* @param {object} end - end date of period
* @param {number} end.year - 4-digit year
* @param {number} end.month - month of year [1-12]
* @param {number} end.day - day of month [1-31]
*
*/
function diffInMonths( bgn , end ) {
const between = ( x , min , max ) => x >= min && x <= max;
// We'll need to add back the final month based on the following:
// - end.day >= bgn.day -- we've passed the month boundary, or
// - end.day is within the end-of-month window
// (when the end month is shorter than the start month)
const needAdjustment = end.day >= bgn.day
|| between( end.day, daysInMonth(bgn), daysInMonth(end) );
const finalMonthAdjustment = needsAdjustment ? 1 : 0;
const deltaM = 12 * ( end.year - bgn.year )
+ ( end.month - bgn.month )
- 1 // remove the final month from the equation
+ finalMonthAdjustment // add in the precomputed final month adjustment
;
return deltaM;
}
/**
*
* @param {object} dt - date
* @param {number} dt.year - 4-digit year
* @param {number} dt.month - month of year [1-12]
* @param {number} dt.day - day of month [1-31]
*
*/
function daysInMonth(dt) {
const leapYear = ( dt.year % 4 === 0 && dt.year % 100 !== 0 ) || dt.year % 400 === 0;
const monthDays = leapYear ? daysPerMonthLeap : daysPerMonth;
const days = monthDays[dt.month];
return days;
}
// jan feb mar apr may jun jul aug sep oct nov dec
// ---------- --- --- --- --- --- --- --- --- --- --- --- ---
const daysPerMonth = [ undefined, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, ];
const daysPerMonthLeap = [ undefined, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, ];