Ellis asks:
I ran into a problem with my app last week and it had me stumped for three days. I think I worked out all the kinks, but I wanted to see if you could take a glance at my code to make sure I'm on the right path. Main issue is with billing dates. When a user signs up on the app they get a billing recurring date which the same day that they sign up on ie 1/31/2010. An error was thrown 2/30/2010 because the date doesn't exist.
I took a look at Ellis' solution, and while it worked, it was quite complex and long and I suggested a much simpler solution that I thought my other readers may enjoy. Obviously there are multiple ways of handling this situation, but I recommended the following pseudo-code as a solution:
Given that a user wants to be billed on date X, and given it is Month M, Year N, what is the best possible match? If M/X/N exists, then use it. If the month doesn't have X days, then use the last day of the month.
I wrote this logic as the following simple UDF:
<cfscript>
function getBillingDate(month,year,day) {
var baseDate = createDate(arguments.year, arguments.month, 1);
if(daysInMonth(baseDate) lt arguments.day) return
createDate(arguments.year, arguments.month, daysInMonth(baseDate));
return createDate(arguments.year, arguments.month, arguments.day);
}
</cfscript>
As you can see, it creates a date based on the passed in year and month. It uses 1 for the day of the month. Once we have that, I simply compare the days in the month to the desired day. If the days in the month is less than the desired date, I use the total number of days in the month. Otherwise - I use the desired date.
To ensure it actually worked, I whipped up a quick test. It runs through five years and a set of desired dates. I intentionally chose dates towards the end of the month to test my logic.
<cfloop index="year" list="2000,2001,2002,2003,2004">
<cfloop index="month" from="1" to="12">
<cfloop index="testDate" array="#tests#">
<cfoutput>
Attempted billing day of #testDate# for #month#/#year# :
#getBillingDate(month,year, testDate)#<br/>
</cfoutput>
</cfloop>
<br/><br/>
</cfloop>
<br/><br/>
</cfloop>
<cfset tests = [1,10,15,30,31]>
I won't bore people with the output from this, but I confirmed it correctly handled February, and also noticed leap years when it could get a bit closer to 30 and 31.
I'm sure there are probably a thousand other ways to handle this, but hopefully this will help others.