Sunday, July 19, 2009

Holidays

Often it is useful to return the date of a holiday for a given year, perhaps for a schedule application. Neither Excel nor VBA have any built in functions for working with holidays -- you have to create your own. Holidays can be thought of as being either Fixed or Floating. Fixed holidays are those that occur on the same day each year, such as Christmas. Floating holidays are those which occur on different days in different years. For example, Thanksgiving (in the US) occurs on the fourth Thursday of November. Therefore, we need a function that will calculate the fourth Thursday. We'll generalize that function, in both VBA and worksheet function form, to return the Nth day-of-week for any month and year.

Other floating holidays are a bit harder to calculate. For example, in the US, Memorial Day occurs on the last Monday of May. Depending on the year, this may be either the 4th or 5th Monday. So we need a function to calculate the number of Mondays in May. We'll generalize this to compute the number of any day-of-week in any month and year.

Finally, there is Easter, whose actual date is some bizarre result of the phases of the moon. I don't claim credit for the formulas for calculating Easter shown below, nor do I claim to understand why the work, but they do.

Fixed Holidays

For fixed holidays, such as Christmas, this is simple since the date of the holiday does not change from year to year. For example, use the following to return the date of Christmas in the current year:

=DATE(YEAR(NOW()),12,25)

Floating Holidays

Other holidays, however, are not assigned to a specific date. For example, Thanksgiving Day is defined to be the 4th Thursday of November. Therefore its exact date will change from year to year. For Thanksgiving, we have an explicit VBA function:

Public Function ThanksgivingDate(Yr As Integer) As Date
ThanksgivingDate = DateSerial(Yr, 11, 29 - _
WeekDay(DateSerial(Yr, 11, 1), vbFriday))
End Function

We can generalize this to holidays that are defined as the Nth Day of some month, such as Martin Luther King's birthday, celebrated on the 3rd Monday of January. The following function will return the Nth DayOfWeek for a given month and year:

Public Function NDow(Y As Integer, M As Integer, _
N As Integer, DOW As Integer) As Date

NDow = DateSerial(Y, M, (8 - WeekDay(DateSerial(Y, M, 1), _
(DOW + 1) Mod 8)) + ((N - 1) * 7))

End Function

To return the date of the 3rd Monday in January of 1998, use
=NDow (1998, 1, 3, 2)

The NDow function can also be written as a worksheet formula:

=DATE(Yr,Mon,1+((Nth-(DoW>=WEEKDAY(DATE(Yr,Mon,1))))*7)
+(DoW-WEEKDAY(DATE(Yr,Mon,1))))

Where Yr,Mon, and DoW are cell references or values indicating Year, Month, Nth, and Day-Of-Week.


This will work fine for most floating holidays. However, Memorial Day is celebrated on the Last Monday of May. To compute this date, we first need a function to tell us how many Mondays there are in the month.

Public Function DOWsInMonth(Yr As Integer, M As Integer, _
DOW As Integer) As Integer

On Error GoTo EndFunction

Dim I As Integer
Dim Lim As Integer
Lim = Day(DateSerial(Yr, M + 1, 0))
DOWsInMonth = 0
For I = 1 To Lim
If WeekDay(DateSerial(Yr, M, I)) = DOW Then
DOWsInMonth = DOWsInMonth + 1
End If
Next I
Exit Function
EndFunction:
DOWsInMonth = 0
End Function



Calling this function will tell us how many Mondays there are in May, 1998.
=DOWsInMonth(1998, 5, 2)

The DOWsInMonth can also be written as a worksheet function with the following array formula. Note that it will not work properly unless you press CTRL+SHIFT+ENTER instead of just ENTER when you first enter the formula and whenever you edit it later. If you do this properly, Excel will display the formula in the formula bar enclosed in curly braces {}.


=SUM((WEEKDAY(DATE(B3,C3,(ROW(INDIRECT
("1:"&DAY(DATE(B3,C3+1,0)))))))=D3)*1)

where B3 is the year, C3 is the month, and D3 is the day of week (1=Sunday, 2=Monday, ..., 7=Saturday)

There are 4 Mondays in May, 1998. The we can pass this into the NDOW function, to return 25-May-1998, the date of Memorial Day. Therefore, the formula to return the date of Memorial Day in 1999 would be

=NDow(1999,5,DowsInMonth(1999,5,2),2)

Easter

Finally, there is Easter, whose date is very complicated to determine from year to year. I did not write the following formula. I don't know who did, but it seems to work.

Public Function EasterDate(Yr As Integer) As Date

Dim d As Integer
d = (((255 - 11 * (Yr Mod 19)) - 21) Mod 30) + 21
EasterDate = DateSerial(Yr, 3, 1) + d + (d > 48) + 6 - ((Yr + Yr \ 4 + _
d + (d > 48) + 1) Mod 7)

End Function

You can also calculate the date of Easter with a worksheet function. For USA style dates (mm/dd/yyyy), use

=FLOOR("5/"&DAY(MINUTE(B2/38)/2+56)&"/"&B2,7)-34

where B2 contains the year for which you want to calculate Easter.

For European style dates (dd/mm/yyyy), use

=FLOOR(DAY(MINUTE(B2/38)/2+56)&"/5/"&B2,7)-34

Another formula, which does not rely on date formatting was submitted by Joost Verbeek.

=FLOOR(DATE(B2,5,DAY(MINUTE(B2/38)/2+56)),7)-34

No comments:

Post a Comment