Monday, January 30, 2012

Finding the first day of a week–ISO 8601

[See http://techmikael.blogspot.com/2012/02/finding-week-number-from-dateiso-8601.html on how to get the correct week number from a date]

I’m working on an application where we work with week numbers and of course there was an error in getting everything to work correct with week numbers. For a specific week I want to get the Monday of that week. With the ISO 8601 standard a week starts on a Monday and ends on a Sunday, and I’m dealing with a calendar which states that week 1 = the first week with 4 full days.

Our current code was not working, and checking on StackOverflow I found a couple of samples in order to fix this:
http://stackoverflow.com/questions/3854429/in-net-knowing-the-week-number-how-can-i-get-the-weekdays-date
http://stackoverflow.com/questions/5377851/get-date-range-by-week-number-c-sharp
http://stackoverflow.com/questions/662379/calculate-date-from-week-number

The flaw in these solutions is that they all base the week calculation on the first Monday of the year. Reading up on ISO 8601 at Wikipedia the solution is simple:
The week number can be described by counting the Thursdays: week 12 contains the 12th Thursday of the year.
Instead of basing the code on a Monday, use Thursday and all boundary conditions with week 1, 52 and 53 are resolved. Then subtract 3 days at the end to get the Monday.

The following code works for all boundary conditions which the previous code samples fail at:

public static DateTime FirstDateOfWeek(int year, int weekOfYear)
{
    DateTime jan1 = new DateTime(year, 1, 1);
    int daysOffset = DayOfWeek.Thursday - jan1.DayOfWeek;

    DateTime firstThursday = jan1.AddDays(daysOffset);
    var cal = CultureInfo.CurrentCulture.Calendar;
    int firstWeek = cal.GetWeekOfYear(firstThursday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);

    var weekNum = weekOfYear;
    if (firstWeek <= 1)
    {
        weekNum -= 1;
    }
    var result = firstThursday.AddDays(weekNum * 7);
    return result.AddDays(-3);
}