/*
There are various ways to do this.
You can put the day of the year of holidays and weekends in an array.
By filtering the array, you can subtract the number of holidays from
the number of actual days that are between two dates,
or account for them when you are adding business days to a starting date.
It gets a little more involved when you span multiple years
with different holiday arrays, but thats what computers are for...
*/
if(!Array.prototype.filter){
Array.prototype.filter= function(fun, scope){
var T= this, A= [], i= 0, itm, L= T.length;
if(typeof fun== 'function'){
while(i<L){
if(i in T){
itm= T[i];
if(fun.call(scope, itm, i, T)) A[A.length]= itm;
}
++i;
}
}
return A;
}
}
Date.prototype.dayOfYear= function(){
var j1= new Date(this);
j1.setMonth(0,0);
return Math.round((this-j1)/8.64e7);
}
// this covers a few years of federal holidays:
var holidates={
y2012:[1, 2, 16, 51, 149, 186, 247, 282, 316, 317, 327, 360],
y2013:[1, 21, 49, 147, 185, 245, 287, 315, 332, 359],
y2014:[1, 20, 48, 146, 185, 244, 286, 315, 331, 359],
y2015:[1, 19, 47, 145, 184, 185, 250, 285, 315, 330, 359],
y2016:[1, 18, 46, 151, 186, 249, 284, 316, 329, 360, 361],
y2017:[1, 2, 16, 20, 51, 149, 185, 247, 282, 314, 315, 327, 359],
y2018:[1, 15, 50, 148, 185, 246, 281, 315, 316, 326, 359],
y2019:[1, 21, 49, 147, 185, 245, 287, 315, 332, 359],
y2020:[1, 20, 48, 146, 185, 186, 251, 286, 316, 331, 360],
y2021:[1, 18, 20, 46, 151, 185, 186, 249, 284, 315, 329, 358, 359],
y2022:[1, 17, 52, 150, 185, 248, 283, 315, 328, 359, 360, 365],
y2023:[1, 2, 16, 51, 149, 185, 247, 282, 314, 315, 327, 359]
}
// return an array of weekends and holidays for a given year,
// or the current year. Each element is the day of the year,
// Jan 1 is 1.
function getOffdays(y){
if(typeof y!= 'number') y= new Date().getFullYear();
var offdays= [], i= 0, firstwk, lastwk, H= holidates['y'+y].slice(0);
var d= 1, year= new Date(y, 0, 1);
while(year.getDay()!= 0) year.setDate(++d);
firstwk= year.dayOfYear();
year.setMonth(11, 31);
d= 31;
if(year.getDay()== 6) lastwk= year.dayOfYear();
else{
while(year.getDay()!= 0) year.setDate(--d);
lastwk= year.dayOfYear();
}
while(firstwk<= lastwk){
offdays.push(firstwk-1, firstwk);
firstwk+= 7;
}
if(offdays[0]== 0) offdays.shift();
if(H) offdays= offdays.concat(H);
return offdays.sort(function(a, b){
return a-b;
});
}
// expects two dates,
// returns the number of business days between them
function bizDays(day1, day2){
var dayfrom= day1, dayto= day2;
if(day1>day2){
dayto= day1;
dayfrom= day2;
}
var offdays= 0, diff= Math.round((dayto-dayfrom)/8.64e7),
d1= dayfrom.dayOfYear(), d2= dayto.dayOfYear(),
y1= dayfrom.getFullYear(), y2= dayto.getFullYear();
if(y1<y2){
offdays= getOffdays(y1).filter(function(d){
return d>= d1;
}).length;
while(y1+1<y2){
offdays+= getOffdays(++y1).length;
}
offdays+= getOffdays(y1).filter(function(d){
return d<= d2;
}).length;
}
else{
offdays= getOffdays(y1).filter(function(d){
return d>= d1 && d<= d2;
}).length;
}
return diff-offdays;
}
// expects an integer and an optional start date-
// uses the current date if no date is specified.
// returns the date that is biz business days after day1
function bizDaysAfter(biz, day1){
var start= day1 || new Date(),
end= new Date(day1), bdiff;
end.setDate(start.getDate()+biz);
bdiff= biz-bizDays(start, end);
while(bdiff>0){
end.setDate(end.getDate()+bdiff);
bdiff= biz-bizDays(start, end);
}
return end;
}
//Some testing:
var D1= new Date(2013, 3, 25), D2= new Date(2013, 4, 15),
days=14,
s1=D1.toLocaleDateString(), s2=D2.toLocaleDateString();
['Business days between '+ s1+' and\n'+s2+': '+bizDays(D1,D2)+' days.',
days+' business days after '+s1+':\n'+
bizDaysAfter(days,D1).toLocaleDateString()].join('\n\n');
/* returned value: (String)
Business days between Thursday, April 25, 2013 and
Wednesday, May 15, 2013: 14 days.
14 business days after Thursday, April 25, 2013:
Wednesday, May 15, 2013
*/