ISO 8601 Week of Year format in Microsoft .Net

Several people have noticed that Calendar.GetWeekOfYear() is almost like the ISO 8601 week when passed CalendarWeekRule.FirstFourDayWeek and DayOfWeek.Monday, however it is a little bit different. Specifically ISO 8601 always has 7 day weeks. If the first partial week of a year doesn't contain Thursday, then it is counted as the last week of the previous year. Likewise, if the last week of the previous year doesn't contain Thursday then its treated like the first week of the next year. GetWeekOfYear() has the first behavior, but not the second. Ie:

Date Day GetWeekOfYear ISO 8601 Week
12/31/2000 Sunday 52 of 2000 52 of 2000
1/1/2001 Monday 1 of 2001 1 of 2001
1/1/2005 Saturday 53 of 2004 53 of 2004
12/31/2007 Monday 53 of 2007 1 of 2008

Notice that the ISO 8601 week at the end of 2007 is different than the GetWeekOfYear() week. For GetWeekOfYear(), both the last week and the first week have fewer than 7 days. Also notice that even though its a 2007 date, its considered the first week of 2008. Similarly the first day of 2005 is considered to be the last week of 2004 by either method.

A simple workaround to consistently get the ISO 8601 week is to realize that consecutive days Monday through Sunday in ISO 8601 weeks all have the same week #. So Monday has the same week # as Thursday. Since Thursday is the critical day for determining when the week starts each year my solution is to add 3 days if the day is Monday, Tuesday or Wednesday. The adjusted days are still in the same week, and use values that GetWeekOfYear and ISO 8601 agree on.

Note that if the requirement is to compute a date in the 2004W536 form, the code will still have to detect that a week 53 in January means that we need to decrement the year by 1, and a week 1 in December requires incrementing the year by 1.

Here's my example. I made this a complete program, however GetIso8601WeekOfYear() is the worker function. I used a static calendar, which probably isn't necessary. Bala pointed out that one could derive a class from GregorianCalendar and override GetWeekOfYear(), but I'm not sure what the repercussions of using such a calendar elsewhere would be. You can try it if you want, but for now this sample is just a simple static method. Main is just here to run through a bunch of days and show when the week calculations differ. 

using System;
using System.Globalization;

class Test
{
// Need a calendar. Culture's irrelevent since we specify start day of week
private static Calendar cal = CultureInfo.InvariantCulture.Calendar;

// This presumes that weeks start with Monday.
// Week 1 is the 1st week of the year with a Thursday in it.
public static int GetIso8601WeekOfYear(DateTime time)
{
// Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll
// be the same week# as whatever Thursday, Friday or Saturday are,
// and we always get those right
DayOfWeek day = cal.GetDayOfWeek(time);
if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
{
time = time.AddDays(3);
}

// Return the week of our adjusted day
return cal.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
}

static void Main(string[] args)
{
// 1/1/1990 starts on a Monday
DateTime dt = new DateTime(1990, 1, 1);
Console.WriteLine("Starting at " + dt + " day: " + cal.GetDayOfWeek(dt) + " Week: " +GetIso8601WeekOfYear(dt));

        for (int i = 0; i < 100000; i++)
{
for (int days = 0; days < 7; dt=dt.AddDays(1), days++)
{
if (GetIso8601WeekOfYear(dt) != cal.GetWeekOfYear(dt, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday))
{
Console.WriteLine("Iso Week " + GetIso8601WeekOfYear(dt) +
" GetWeekOfYear: " + cal.GetWeekOfYear(dt, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday) +
" Date: " + dt + " Day: " + cal.GetDayOfWeek(dt));
}
}
}
}
}