One disadvantage of supplying an array of holiday dates to the toCalendar method is that the array can only hold dates for a specific period. If you want to display a calendar outside of that period then you need to add the holidays for that year to the array or you will not be able to highlight the holidays.

Adding that one extra line to the toCalendar method to allow a function to be supplied instead of an array mostly resolves this problem. It doesn't completely resolve it because a function to calculate when the holidays should be in a given year will be dependent on those rules actually being followed in which holidays actually are declared for that year. Still we should be able to get reasonably close allowing for the fact that our function will not be able to identify any special public holidays added for just the one year and will also not be able to omit any holidays that for some reason doesn't occur in a specific year.

Most holidays follow a specific set of rules so that we should be able to apply those rules in order to be able to determine what specific date that holiday was on in a specified year. Before we start though we need to remember that public holidays are specific to a particular location and so in producing your list of holiday dates you need to have a location in mind (this applies even if you are simply creating a static array of dates). You will therefore need to write your own custom function that generates an array of public holidays for your selected location.

For the purpose of this example we'll consider the public holidays that generally apply in the state of New South Wales in Australia.

Holidays here either occur on a specific date or on a particular Monday in a specific month. With some of the holidays where the date itself falls on a weekend the following Monday is also a holiday.

Adding the specific dates to the array is straightforward - we simply take the supplied year and concatenate the month and day to give us our ccyy-mm-dd string. For those holidays that occur on a specific Monday we create a nextMonday function that takes a specified day of the month and if that isn't aMonday will return the day of the month following that date that is the next Monday. By supplying a start day of 1 we can retrieve the first Monday, 8 gives us the second Monday, 15 gives us the third Monday and 22 gives us the fourth Monday of the Month via this function. To test if a date is on a weekend we need simply test if the day of the week is 0 or 6. We can then set the following day to also be a holiday (or where that is still weekend we can set the day after to be a holiday).

So 1st January is the new year's day holiday and if that is on a weekend then Monday the 2nd or 3rd will also be a holiday. 26th January is the Australia Day holiday but in recent years an additional holiday has not been provided if it falls on a weekend. The Queen's birthday holiday is held in NSW on the second Monday in June. So we apply the nextMonday and isWeekend functions as appropriate to obtain all these dates.

That just leaves Easter as the one group of holiday dates that are not catered for by the two functions we already have. So we simply add one extra function that will determine when Easter Day is in the specific year and which returns the month and day as a two element array. Here we have three holiday dates since Good Friday is also a public holiday and so is the Monday following (to make up for Easter Day always being on the weekend).

So taking all that into account and setting up appropriate rules for generating all the holiday dates for the year and saving them to the array we end up with the following code that is specific to public holidays in NSW, Australia.

holidays = function(yr) {

"use strict";

var h, e, easter, nextMonday, isWeekend;

easter = function(year) {

var a,b,c,d,e,f,g,h,i,j,k,m;

a = year % 19;

b = Math.floor(year/100);

c = year % 100;

d = Math.floor(b/4);

e = b % 4;

f = Math.floor((b+8) / 25);

g = Math.floor((b-f+1) / 3);

h = (19*a + b - d - g + 15) % 30;

i = Math.floor(c/4);

j = c % 4;

k = (32 + 2*e + 2*i - h - j) % 7;

m = Math.floor((a + 11*h + 22*k) / 451);

return [Math.floor((h + k - 7*m + 114) / 31),((h + k - 7*m +114) % 31) + 1];

}

nextMonday = function(yr, mth, dy) {

var dt, dw;

dt = new Date(yr, mth-1, dy);

dw = dt.getDay();

if (dw > 1) return dy + 8 - dw;

return dy + 1 - dw;

}

isWeekend = function(yr, mth, dy) {

var dt, dw;

dt = new Date(yr, mth-1, dy);

dw = dt.getDay();

return (0 === dw || 6 === dw);

}

h = [];

// New Year's Day (+ extra if on weekend)

h.push(yr + '-01-01');

if (isWeekend(yr,1,1)) {

if (isWeekend(yr,1,3))

h.push(yr + '-01-03');

else

h.push(yr + '-01-02');

}

// Australia Day

h.push(yr + '-01-26');

// Easter Day

e = easter(yr);

h.push(yr + '-0' + e[0] + '-' + ((e[1]>9)?e[1]:'0'+e[1]));

// Good Friday

if (e[1] < 3) {e[0]--; e[1]+=29;}

else e[1]-=2;

h.push(yr + '-0' + e[0] + '-' + ((e[1]>9)?e[1]:'0'+e[1]));

// Easter Monday

if (e[1] > 28) {e[0]++; e[1]-=28;}

else e[1]+=3;

h.push(yr + '-0' + e[0] + '-' + ((e[1]>9)?e[1]:'0'+e[1]));

// Anzac Day

h.push(yr + '-04-25');

// Queen's Birthday Holiday (2nd Monday June)

e = nextMonday(yr, 6, 8);

h.push(yr + '-06-' + ((e>9)?e:'0'+e));

// Labour Day (1st Monday October)

e = nextMonday(yr, 10, 1);

h.push(yr + '-10-' + ((e>9)?e:'0'+e));

// Christmas Day (+ extra if on weekend)

h.push(yr + '-12-25');

// Boxing Day (+ extra if on weekend)

h.push(yr + '-12-26');

if (isWeekend(yr,12,25)) h.push(yr + '-12-27');

if (isWeekend(yr,12,26)) h.push(yr + '-12-28');

return h;

}

holidays = holidays.memoize();

"use strict";

var h, e, easter, nextMonday, isWeekend;

easter = function(year) {

var a,b,c,d,e,f,g,h,i,j,k,m;

a = year % 19;

b = Math.floor(year/100);

c = year % 100;

d = Math.floor(b/4);

e = b % 4;

f = Math.floor((b+8) / 25);

g = Math.floor((b-f+1) / 3);

h = (19*a + b - d - g + 15) % 30;

i = Math.floor(c/4);

j = c % 4;

k = (32 + 2*e + 2*i - h - j) % 7;

m = Math.floor((a + 11*h + 22*k) / 451);

return [Math.floor((h + k - 7*m + 114) / 31),((h + k - 7*m +114) % 31) + 1];

}

nextMonday = function(yr, mth, dy) {

var dt, dw;

dt = new Date(yr, mth-1, dy);

dw = dt.getDay();

if (dw > 1) return dy + 8 - dw;

return dy + 1 - dw;

}

isWeekend = function(yr, mth, dy) {

var dt, dw;

dt = new Date(yr, mth-1, dy);

dw = dt.getDay();

return (0 === dw || 6 === dw);

}

h = [];

// New Year's Day (+ extra if on weekend)

h.push(yr + '-01-01');

if (isWeekend(yr,1,1)) {

if (isWeekend(yr,1,3))

h.push(yr + '-01-03');

else

h.push(yr + '-01-02');

}

// Australia Day

h.push(yr + '-01-26');

// Easter Day

e = easter(yr);

h.push(yr + '-0' + e[0] + '-' + ((e[1]>9)?e[1]:'0'+e[1]));

// Good Friday

if (e[1] < 3) {e[0]--; e[1]+=29;}

else e[1]-=2;

h.push(yr + '-0' + e[0] + '-' + ((e[1]>9)?e[1]:'0'+e[1]));

// Easter Monday

if (e[1] > 28) {e[0]++; e[1]-=28;}

else e[1]+=3;

h.push(yr + '-0' + e[0] + '-' + ((e[1]>9)?e[1]:'0'+e[1]));

// Anzac Day

h.push(yr + '-04-25');

// Queen's Birthday Holiday (2nd Monday June)

e = nextMonday(yr, 6, 8);

h.push(yr + '-06-' + ((e>9)?e:'0'+e));

// Labour Day (1st Monday October)

e = nextMonday(yr, 10, 1);

h.push(yr + '-10-' + ((e>9)?e:'0'+e));

// Christmas Day (+ extra if on weekend)

h.push(yr + '-12-25');

// Boxing Day (+ extra if on weekend)

h.push(yr + '-12-26');

if (isWeekend(yr,12,25)) h.push(yr + '-12-27');

if (isWeekend(yr,12,26)) h.push(yr + '-12-28');

return h;

}

holidays = holidays.memoize();

Well there is one extra line on the end of that code that isn't a part of the holidays function itself. The one problem with using a function to generate our array of holiday dates is that we would be regenerating the same dates over and over if we swap the calendar between months in the same year. To avoid this we memoize the function. What this does is to store the results from each call to the function and if the function is called again with the same parameters it simply returns the stored result from the prior call with those parameters rather than recalculating the same values. This only works where the same call always produces the same result - which is the case here. So by momoizing our function for creating an array of holiday dates for a specified year we end up with the function only ever being run once for any specified year no matter how many times that calendars from that year are displayed. All of the subsequent calendars will simply retrieve the array that was built when the function was called the first time the holidays for that year were requested.

*This article written by Stephen Chapman, Felgall Pty Ltd.*