Hot dates for a Saturday night. - Handling Business Days In Flex
Well, sorta. Let's say that you have some known starting day and an event that happens some N days later. And, just for giggles, let's assume that every single one of those days must occur during a valid business day; no weekends, no holidays. How do we go about this in Flex?
My first version, the exact details you'll be spared because of my kind generosity, was overly complicated. This was mostly because I intended to analyze date ranges and do crazy parsing to determine by how much N had to be increased in order to account for any non-work day. I had worked out the bulk of the details but, with multiple passes, it was beginning to look like some 16th century model of the solar system.
Alex had a better suggestion. Instead of trying to look at the whole range, we do a single forward loop, adding each of the N offset days individually, testing as we go. The devil is in the details, of course, but I've broken it down into 6 handy little functions. For my purposes, making a class of static methods seemed pretty reasonable as I wanted to be able to reuse these utilities at will and I didn't see the point instantiation. The methods will be:
- public static function getDateAdjustedForBreaks(begin:Date,offset:int):Date
- public static function makeValidBusinessDay(target:Date):Date
- public static function isBusinessDay(target:Date):Boolean
- public static function isWeekday(target:Date):Boolean
- public static function isHoliday(target:Date):Boolean
- public static function getHolidays():Array
In addition to this static functions, we'll rely on a constant to make our life a little bit easier:
private static const MS_PER_DAY:int = 1000 * 60 * 60 * 24;
When dealing with dates, it is often easier to work with the native millisecond count, and MS_PER_DAY helps.
getDateAdjustedForBreaks is the method that really ties everything together, so I'll work from the bottom up, starting with the consideration of holidays. In practice, it might be easier to make the holiday day accessible from an external file or remote object call, but for the sake of simplicity, I'll express the model as a simple array.
getHolidays()
public static function getHolidays():Array {
var _holidays:Array = [];
// remember that the months go from 0 (january) to 11 (december)
// 2009
_holidays.push(new Date(2009,0,1) ); // New Years
_holidays.push(new Date(2009,4,25) ); // Memorial Day
_holidays.push(new Date(2009,6,4) ); // Independence Day
_holidays.push(new Date(2009,8,7) ); // Labor Day
_holidays.push(new Date(2009,10,26) ); // Thanksgiving
_holidays.push(new Date(2009,10,27) ); // Day after Thanksgiving
_holidays.push(new Date(2009,11,24) ); // Christmas Eve
_holidays.push(new Date(2009,11,25) ); // Christmas Day
// 2010
_holidays.push(new Date(2010,0,1) ); // New Years
_holidays.push(new Date(2010,4,31) ); // Memorial Day
_holidays.push(new Date(2010,6,5) ); // Independence Day
_holidays.push(new Date(2010,8,6) ); // Labor Day
_holidays.push(new Date(2010,10,25) ); // Thanksgiving
_holidays.push(new Date(2010,10,26) ); // Day after Thanksgiving
_holidays.push(new Date(2010,11,24) ); // Christmas Eve
_holidays.push(new Date(2010,11,24) ); // Christmas Day (falls on saturday)
return _holidays;
}
This ought not to require a lot of discussion. We just build a simple array of dates indicating all of the holidays that interest us. We consider weekends separately, so don't include any weekend holidays in this list. And, like I mentioned above, feel free to get creative on how you populate this array.
isHoliday()
public static function isHoliday(target:Date):Boolean {
var _holidays:Array = getHolidays();
target.setHours(0,0,0,0);
for (var d:int=0;d<_holidays.length;d++) {
if ( _holidays[d].time == target.time) {
return true;
break;
}
}
return false;
}
Starting with grabbing the the holidays, using the neat little function described a moment ago, we next set the time on our target to midnight. By setting the time component of the date to all zeros, we are able to do simple date compares. After that, it is a simple matter of iterating over the list of holidays and performing that simple compare. We break early if we find a matching holiday.
isWeekday()
public static function isWeekday(target:Date):Boolean {
if (target.day < 1 || target.day > 5) {
return false;
}
return true;
}
This is another simple one. For reasons that probably make a lot of sense the days of the week run 0 to 6 starting with Sunday. This is a bit annoying for weekend consideration as the weekend days represent end points. Anyway, the comparison is easy enough.
Onwards towards business days.
isBusinessDay()
public static function isBusinessDay(target:Date):Boolean {
target.setHours(0,0,0,0);
var _returnBizDay:Boolean = false;
if ( isWeekday(target) && !isHoliday(target) ) {
_returnBizDay = true;
}
return _returnBizDay;
}
So first thing is that we set the time to zeros to make date comparison easier. The _returnBizDay is created to handle our response. We assume that it is going to not pan out so the default is false. The actual logic is straightforward after that. We use the isWeekday and isHoliday to handle the control.
makeValidBusinessDay()
public static function makeValidBusinessDay(target:Date):Date {
while (!isBusinessDay(target) ) {
target = new Date( target.time + MS_PER_DAY );
}
return target;
}
This is really the workhorse of our effort. We start with the comparison to make sure the day is a valid business day. If it is, we just move along. If not, we add a day and check again. This way, we add as many days as needed to ensure that the date does not fall on a weekend or holiday.
And, to bring it all together ...
getDateAdjustedForBreaks()
public static function getDateAdjustedForBreaks(begin:Date,offset:int):Date {
var _adjustedDate:Date;
_adjustedDate = new Date(begin.time);
_adjustedDate.setHours(0,0,0,0);
if (offset > 0) {
for (var d:int=1;d<=offset;d++) {
_adjustedDate = new Date(_adjustedDate.time + (MS_PER_DAY) );
_adjustedDate = makeValidBusinessDay(_adjustedDate);
}
}
else {
_adjustedDate = makeValidBusinessDay(_adjustedDate);
}
return _adjustedDate;
}
My actual method is a wee bit more complicated as my particular problem is more painful, but this is the simplified version. _adjustedDate is our return date and we begin by setting the _adjustedDate to the begin date and then setting the time to midnight. On the off chance the offset number of days is 0, we bypass the loop and just ensure that the day is a valid business day. If we didn't do this fork in the road, the iteration over the loop would have been a lot of more painful.
Assuming we are offsetting the date by an amount greater than zero, we loop. For each iteration, we add a day to the date and then ensure the day is a valid business day. By looping over the days, we ensure that each one is considered.
So all of this makes for a useful static class.


There are no comments for this entry.
[Add Comment]