Friday, February 03, 2012

Implement a Week Iterator in C# using DateTime, IEnumerable and Extension Methods

I had a problem to solve. It wasn't a difficult problem, it was just one of those yucky problems I've done numerous times before and for some reason just always feels hacky.

The problem was this: For a given date range I wanted a list containing the date of the start of each week within that range. To be clear: The first date did not need to be within the range. Rather, the first date would normally be the Sunday of the week in which the first date fell.

This isn't an unusual thing to do and it is something that is done often in reporting tools.

This is also the perfect case study for writing an Extension method on DateTime that returns an IEnumerable. Doing this results in an easy, elegant and reusable solution that is no longer a hacky function buried in a class somewhere, but an extension to an already ubiquitous class (DateTime).

First up, this is implemented uses an Extension Method on the DateTime class. In C# extension methods allow you to add methods to an existing class without having to derive from the class or change and recompile the class. A couple of things about Extension Methods:
  • They are always static (that includes class in which they are defined and the method itself)
  • The first parameter is always the class which the method operates on proceeded by this.
  • Extensions are only in scope when you import the namespace in which the method is defined with a using statement. 

So, the prototype for my new extension method will look something like this (I'm calling the method EachWeek):

namespace Extensions
{
  public static class MyExtensions
  {
    public static IEnumerable EachWeek(this DateTime start, 
    DateTime end)
    {
       ...
    }
  }
}

In our calling code, this means I can now use this function as follows:

using Extensions

...
DateTime endDate = DateTime.Now.AddMonths(2);
IEnumerable weeks = DateTime.Now.EachWeek(endDate);

Iterating over weeks will now return each of the Sundays from the first Sunday before DateTime.Now to the last Sunday before endDate.

Using the above template, it's now simple to extend this to implement EachDay, EachMonth and EachYear.


The implementation of the function itself is as follows:

public static IEnumerable EachWeek(this DateTime start, DateTime end)
{
  // Remove time info from start date (we only care about day).
  DateTime currentDay = new DateTime(start.Year, start.Month,

  start.Day);
           
  // Get the dayOffset and subtract this from the current day,
  // this will give us the start of the first week.
  int dayOffset = (int)start.DayOfWeek;
  DateTime currentWeek = currentDay.Subtract(new TimeSpan(

  dayOffset, 0, 0, 0));
  while (currentWeek < end)
  {
    yield return currentWeek;
    currentWeek = currentWeek.AddDays(7);
  }
}

For more information on Extension methods see:
http://msdn.microsoft.com/en-us/library/bb383977.aspx
http://msdn.microsoft.com/en-us/library/bb311042.aspx

For more information on IEnumerable and Yield return see:
http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx
http://www.dotnetperls.com/yield