Friday, November 21, 2008
Calculating Floating Holidays

When rendering a calendar's grid or creating a company's HR days-off list, figuring out New Years Day, Christmas and Valentines Day are easy, but what about those floating holidays that depend on the instance of the Day in the Month?

This set of utilities gives a dev a lot of options plus offers protection against accidental weird arguments.

What's more, now all floating Holidays a company cares about are simply defined in a config file with month, instance number and/or flag for relative instance position from start or end of the month. (Non-floating holidays are likewise easily defined with a flag for specific Day number.)

Example test harness: start a Winform project, copy the class code to a class file, drop two textboxes on the form (txtDate and txtInstance), add a button to the form, double click on the button and paste this code in the stub:

Private Sub butTest_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
   Handles butTest.Click

   If IsDate(txtDate.Text) Then
       Try
           MsgBox(DemoDateUtils.GetInstanceofWeekdayinMonth( _
              CDate(txtDate.Text), DayOfWeek.Sunday, CInt(txtInstance.Text) _
              ))
       Catch ex As Exception
           MsgBox(ex.ToString)
       End Try
   End If

   ''another way the dev can now call it, by checking the instance count first

   If IsDate(txtDate.Text) Then
       Try
           If CInt(txtInstance.Text) > DemoDateUtils.GetInstanceCountOfWeekdayInMonth( _
               CDate(txtDate.Text), DayOfWeek.Sunday) Then
               MsgBox("I don't even have to call it, there aren't that number of instances")
           Else
               MsgBox(DemoDateUtils.GetInstanceofWeekdayinMonth( _
                   CDate(txtDate.Text), DayOfWeek.Sunday, CInt(txtInstance.Text)))

           End If
       Catch ex As Exception
           MsgBox(ex.ToString)
       End Try

   End If
 

   ''what is memorial day?  last monday in May?

   MsgBox("Memorial day this year is: " & _
       DemoDateUtils.GetLastInstancedDayOfMonth( _
           New DateTime(Now.Year, 5, 1), _
           DayOfWeek.Monday).ToLongDateString)


   ''and when is Mothers day?  2nd sunday in May?

   MsgBox("Mothers day day this year was: " & _
      DemoDateUtils.GetInstanceofWeekDayinMonth( _
      New DateTime(Now.Year, 5, 1), DayOfWeek.Sunday, 2).ToLongDateString)

End Sub

Similarly, when filling a Calendar control just read celebrated holidays from an xml file and test each day on the DayRender event, setting the Text to add a special icon or flyover tooltip or different backcolor to the cells that have holidays.

Here's the class code for floating Holidates in VB and C#:

VB code:

Public Class DemoDateUtils

    Public Shared Function GetInstanceofWeekdayinMonth( _
         ByVal dt As Date, _
         ByVal weekday As DayOfWeek, _
         ByVal instance As Integer) As Date

        If instance <= 0 Then
            Throw New ArgumentException("Instance count must be greater than zero", _
	      "instance")
        End If

        Dim dtRet As DateTime

        Dim dtFirstDay As DateTime = _
            GetFirstInstancedDayOfMonth(dt, weekday)

        Dim instancesInMonth As Integer = GetInstanceCountOfWeekdayInMonth(dt, weekday)

        If instance <= instancesInMonth Then
            Dim padDays As Integer = 7 * (instance - 1)
            dtRet = New DateTime(dt.Year, dt.Month, dtFirstDay.Day + padDays)
        Else
            Throw New ArgumentException("Instance range exceeded, _
	      max: " & instancesInMonth.ToString, "instance")
        End If

        Return dtRet
 

    End Function


    Public Shared Function GetInstanceCountOfWeekdayInMonth( _
        ByVal dt As Date, _
        ByVal weekday As DayOfWeek) As Integer

        Return ((GetLastInstancedDayOfMonth(dt, weekday).Day - 
	   GetFirstInstancedDayOfMonth(dt, weekday).Day) \ 7) + 1

    End Function


    Public Shared Function GetFirstInstancedDayOfMonth( _
        ByVal dt As Date, _
        ByVal weekday As DayOfWeek) As DateTime


        Dim dtFirstDay As DateTime = _
            New DateTime(dt.Year, dt.Month, 1)

        If weekday < dtFirstDay.DayOfWeek Then
            dtFirstDay = dtFirstDay.AddDays(weekday - dtFirstDay.DayOfWeek + 7)
        Else
            dtFirstDay = dtFirstDay.AddDays(weekday - dtFirstDay.DayOfWeek)
        End If

        Return dtFirstDay

    End Function


    Public Shared Function GetLastInstancedDayOfMonth( _
        ByVal dt As Date, _
        ByVal weekday As DayOfWeek) As DateTime


        Dim dtLastDay As DateTime = _
            New DateTime(dt.Year, dt.Month, 1).AddMonths(1).AddDays(-1)

        If weekday > dtLastDay.DayOfWeek Then
            dtLastDay = dtLastDay.AddDays(weekday - dtLastDay.DayOfWeek - 7)
        Else
            dtLastDay = dtLastDay.AddDays(weekday - dtLastDay.DayOfWeek)
        End If
        Return dtLastDay

   End Function


End Class

 

C# code:

using System;

public class csDemoDateUtils
{

    public static DateTime GetInstanceofWeekdayInMonth(DateTime dt, 
       DayOfWeek weekday, int instance)
    {
        if (instance <= 0)
        {
            throw new ArgumentException("Instance count must be greater than zero", 
	      "instance");
        }

        DateTime dtRet;

        DateTime dtFirstDay = GetFirstInstancedDayOfMonth(dt, weekday);

        int instancesInMonth = GetInstanceCountOfWeekdayInMonth(dt, weekday);

        if (instance <= instancesInMonth)
        {
            int padDays = 7 * (instance - 1);
            dtRet = new DateTime(dt.Year, dt.Month, dtFirstDay.Day + padDays);
        }
        else
        {
            throw new ArgumentException("Instance range exceeded, max: " + 
	      instancesInMonth.ToString(), "instance");
        }

        return dtRet;

    }


    public static int GetInstanceCountOfWeekdayInMonth(DateTime dt, DayOfWeek weekday)
    {
        return ((GetLastInstancedDayOfMonth(dt, weekday).Day - 
	   GetFirstInstancedDayOfMonth(dt, weekday).Day) / 7) + 1;

    }


    public static DateTime GetFirstInstancedDayOfMonth(DateTime dt, DayOfWeek weekday)
    {
        DateTime dtFirstDay = new DateTime(dt.Year, dt.Month, 1);

        if (weekday < dtFirstDay.DayOfWeek)
        {
            dtFirstDay = dtFirstDay.AddDays(weekday - dtFirstDay.DayOfWeek + 7);
        }
        else
        {
            dtFirstDay = dtFirstDay.AddDays(weekday - dtFirstDay.DayOfWeek);
        }
        return dtFirstDay;

    }
 

    public static DateTime GetLastInstancedDayOfMonth(DateTime dt, DayOfWeek weekday)
    {
        DateTime dtLastDay = new DateTime(dt.Year, dt.Month, 1).AddMonths(1).AddDays(-1);

        if (weekday > dtLastDay.DayOfWeek)
        {
            dtLastDay = dtLastDay.AddDays(weekday - dtLastDay.DayOfWeek - 7);
        }
        else
        {
            dtLastDay = dtLastDay.AddDays(weekday - dtLastDay.DayOfWeek);
        }

        return dtLastDay;

    }
}

 
Hope that helps.

Robert Smith
Kirkland, WA

added February 2006


Print  

pagecomment
  Add Comment



Submit Comment
  View Ratings
50.00%0
40.00%0
30.00%0
20.00%0
10.00%0

Number of Comments 0 , Average of Ratings
  View Comments
No comment.


Privacy Statement  |  Terms Of Use
Copyright 2008 by Robert C. Smith