ISO weeknumbers of a date, a C# implementation

The DateTime class in the .NET framework is very rich in methods for manipulating and computing dates. In my former language Delphi working with dates was no fun. All had to be done by manipulating strings but given the fact that nobody can agree on what comes first, day or month, all code was tricky and prone to bugs.


The only thing which is missing in the class is a weeknumber property. Every workweek has its own number. In Europe this is very often used. There is an ISO 8601 standard which describes how this number is calculated: Week 01 of a year is per definition the first week that has the Thursday in this year, which is equivalent to the week that contains the fourth day of January.


There are plenty of implementations to be found on the web to calculate this number. Finding a good one in C# took some assembling. This one is based on a Delphi implementation on the SDN site. Thanks to the DateTime class it needs only a fraction of the code.


        private int weekNumber(DateTime fromDate)
        {
            // Get jan 1st of the year
            DateTime startOfYear = fromDate.AddDays(- fromDate.Day + 1).AddMonths(- fromDate.Month +1);
            // Get dec 31st of the year
            DateTime endOfYear = startOfYear.AddYears(1).AddDays(-1);
            // ISO 8601 weeks start with Monday
            // The first week of a year includes the first Thursday
            // DayOfWeek returns 0 for sunday up to 6 for saterday
            int[] iso8601Correction = {6,7,8,9,10,4,5};
            int nds = fromDate.Subtract(startOfYear).Days  + iso8601Correction[(int)startOfYear.DayOfWeek];
            int wk = nds / 7;
            switch(wk)
            {
                case 0 :
                    // Return weeknumber of dec 31st of the previous year
                    return weekNumber(startOfYear.AddDays(-1));
                case 53 :
                    // If dec 31st falls before thursday it is week 01 of next year
                    if (endOfYear.DayOfWeek < DayOfWeek.Thursday)
                        return 1;
                    else
                        return wk;
                default : return wk;
            }
        }
 

The nice thing I used from the Delphi sample was the iso8601 correction array using the day of the week as index.


It would be even nicer to add the method as a property to a derived DateTime class. Alas that class is sealed.

This entry was posted in Coding. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • PeterGekko

    Testing on sunday would be wrong. Also a friday or a saturday in the last week of december can be in week 1

  • PeterGekko

    I tested it once again (actually the code is still in full production) The code returns the weeknumbers as decribed by ISO for both dates. Indeed 52 in both cases. Your observation seems to be incorrect…

  • Jurjen

    Great code, just wondering why the weeknumber isn’t correct when using 2011-01-01 or 2012-01-01, both weeknumber should be 52 but the code results in week number 1.

    Jurjen.

  • Tapio

    Hi,

    Shouldn’t it be instead of this:

    // If dec 31st falls before thursday it is week 01 of next year
    if (endOfYear.DayOfWeek DayOfWeek.Sunday &&
         endOfYear.DayOfWeek < DayOfWeek.Thursday)
       return 1;

    I think the previous statement might fail because DayOfWeek.Sunday is the last day of the week, but it is aso smaller (0) that DayOfWeek.Thursday

    But maybe i didn get everything :-)


    write me at: tapio.vorlund@kolumbus.fi

  • Ysteiger

    Hi, great article. Helped me very much!! Thanks a lot and happy new year!!

  • Kishore kharche

    Hi This is very good artical. Its work fine. Thanks

  • Leonardo Martins

    Thanks, the codes of c# I find not work, but this code is very nice.

  • Cody

    Testing

  • Cody

    But is it possible make this work in the original monthCalender control?
    How to link it to the control?

  • Mark

    Here is my C# method which passes all of the test cases on

    http://en.wikipedia.org/wiki/ISO_week_date

    public class ISO_8601 {
    public int year;
    public int week;
    public int dayOfWeek;

    public ISO_8601(DateTime dt)
    {
    dayOfWeek = 1 + ((int)dt.DayOfWeek + 1+5) % 7; // Mon=1 to Sun=7
    DateTime NearestThu = dt.AddDays(4 – dayOfWeek);
    year = NearestThu.Year;
    DateTime Jan1 = new DateTime(year, 1, 1);
    TimeSpan ts = NearestThu.Subtract(Jan1);
    week = 1 + ts.Days / 7; // Count of Thursdays
    }
    }

  • doiremik

    jonelf, this wont work either as it still generates 53. It does seem to work for the start of the years though so it might need modified for week 53. Can believe Microsoft hasnt fixed this when is knocking about since the start of this blog…

  • http://alicebobandmallory.com/ jonelf

    Here’s a hack from Shawn Steele that also gives the ISO 8601 week.
    // 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
    http://blogs.msdn.com/shawnste/archive/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net.aspx

  • http://alicebobandmallory.com/ jonelf

    Thank you Peter, that’s exactly what I tried to say.

    A couple of minutes ago I tried the same in Mono and it also returns week 53.

    PS. Totally unrelated but Ruby gets it right:
    >> Date.parse(“2008-12-31″).strftime(“%V”)
    => “01″
    DS.

  • Peter

    The point in jonelf’s comment is not that there can’t be 53 weeks in a year (which is possible as you say according to ISO 8601) but rather that 2008-12-31 was a Wednesday which makes 2009-01-01 a Thursday which makes that week number 1 and not 53.
    See http://en.wikipedia.org/wiki/ISO_week_date for more information.

  • http://petersgekko.codebetter.com pvanooijen

    As I read it ISO 8601 does not say anything about week 53, it just defines when week 1 starts.Which might result in a year with 53 weks.

  • http://alicebobandmallory.com/ jonelf

    System.Globalization.CultureInfo myCI = System.Threading.Thread.CurrentThread.CurrentCulture;

    int weekNo = myCI.Calendar.GetWeekOfYear(
    new DateTime(2008,12,31), myCI.DateTimeFormat.CalendarWeekRule, myCI.DateTimeFormat.FirstDayOfWeek);

    The above sets weekNo to 53. This is not correct according to ISO 8601. Seems the framework misses a parameter that says that only full weeks are allowed.

    The VB.NET DateAndTime fails in the same way.

  • Yves Goergen

    Is there a way to reverse that calculation? The only thing I find on the web is converting date to week number. Now when the user enters a year and a week number, I need to get the Monday (first day) of that week. How can that be done? Try, error and iterate?

    Please reply via e-mail to nospam@unclassified.de.

  • http://codebetter.com/blogs/peter.van.ooijen/ pvanooijen

    @ Herbee: I miss your point. My point is that there are several definitions for first day of the week around, one of them is ISO.
    I do agree that’s the one to follow but, as written in the original post, not al software producing week numbers follows ISO (yet).
    For the first week of the year it’s of no importance which day of the week comes first. First thirsday, as ISO says.

  • Herbee

    … but pvanooijen’s opinion doesn’t matter in this case. ISO 8601, section 3.28, defines Monday as the first day of the week.

  • http://codebetter.com/blogs/peter.van.ooijen/ pvanooijen

    @ Goffen: I don’t agree.

    ISO 8601 states “first thursday”, not your quote. How many days in a week that is depends on which day is day 1 of the week. Opinions on that vary !

  • Goffen

    NONE of your code work correctly for instance for the the last week of 2003 See http://konsulent.sandelien.no/VB_help/Week/

    (also for a solution in vb/c#)

    According to the ISO 8601 rulebook, the week number of the transitionweek
    between two years, is linked to the year in which that week has most days.
    The final week of 2003 / first week of 2004 had three days in 2003 and four
    days in 2004 and should therefore in its entirety have been associated with
    2004 (as week number 1).

  • http://codebetter.com/blogs/peter.van.ooijen/ pvanooijen

    OK, who needs VB ?
    It’s all in the framework.

    A variation which adapts itself to the culture of the runtime environment :
    System.Globalization.CultureInfo myCI = System.Threading.Thread.CurrentThread.CurrentCulture;
    int weekNo = myCI.Calendar.GetWeekOfYear(date, myCI.DateTimeFormat.CalendarWeekRule, myCI.DateTimeFormat.FirstDayOfWeek);

    For the Dutch code CalenderWeekRule and FirstDayOfWeek comply with ISO.

    Thanks.

  • http://weblogs.asp.net/yreynhout Yves Reynhout

    Ever checked out System.Globalization?
    In this example CalendarWeekRule and DayOfWeek should be thought of as configurable.

    public static int GetWeekOfYear(DateTime date){
    return System.Threading.Thread.CurrentThread.CurrentCulture.Calendar.GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
    }

  • http://codebetter.com/blogs/peter.van.ooijen/ pvanooijen

    There are indeed different ways to calculate these, with the many parameters of the VB DatePart function you can get any result you want. ISO is trying to standardize the weeknumbers by explicitly stating how to calculate. My implementation folllows ISO.
    In the Netherlands week numbers are often used, since the ISO standard is there people are beginning to agree on them. Before that there were differences. Leading to b2b misunderstandings.

  • http://austegard.blogspot.com Oskar Austegard

    Keep in mind that there are different rules for calculating weeknumbers, depending on location. The issue is with what week to consider as the first week of the year (is it the first complete week, or the first week that has the majority of days in the new year?, etc).

    A few years ago, my then-employer’s European divisions requested weeknumbers in our intranet application’s calendar controls. At the time there were no controls I could find that would handle this. But I did some research and contacted Peter Blum, who had provided the Date package controls we were using. He was very open to adding the feature to the controls, and I was more than happy to test the added features.

    A screenshot of the week number column can be seen half-way down the page here: http://www.peterblum.com/DateControls/CSCalendarHome.aspx

  • http://codebetter.com/blogs/peter.van.ooijen/ pvanooijen

    That will work as well. You can even use it in C#.

    Add Microsoft.VisualBasic to the reference of your C# project

    Add this to the uses clause

    using Microsoft.VisualBasic;

    An alternative implementation would be :

    private int weekNumberVB(DateTime fromDate) {
    return DateAndTime.DatePart(DateInterval.WeekOfYear, fromDate, Microsoft.VisualBasic.FirstDayOfWeek.Monday, FirstWeekOfYear.FirstFourDays);

    }

    FirstDayOfWeek is ambigious, you have to fully qualify that one.

    Nice :) And even shorter.

  • http://www.tapmymind.com daughtkom

    In VB, you can do this:

    DateAndTime.DatePart(DateInterval.WeekOfYear, DateTime.Now, FirstDayOfWeek.Sunday, FirstWeekOfYear.FirstFourDays)

    The DateAndTime module is in the Microsoft.VisualBasic namespace, and syntax for the DatePart function is as follows (from MSDN):

    Public Overloads Function DatePart( _
    ByVal Interval As String, _
    ByVal DateValue As Object, _
    Optional ByVal DayOfWeek As FirstDayOfWeek = FirstDayOfWeek.Sunday, _
    Optional ByVal WeekOfYear As FirstWeekOfYear = FirstWeekOfYear.Jan1 _
    ) As Integer

    The choices for FirstWeekOfYear are:
    FirstWeekOfYear.System
    FirstWeekOfYear.Jan1
    FirstWeekOfYear.FirstFourDays
    FirstWeekOfYear.FirstFullWeek

    Am I missing something else, or could you just use Microsoft.VisualBasic.DateAndTime.DatePart(…) to do this? To be honest, I’ve never had occasion to use week number myself.