Freigeben über


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));
}
}
}
}
}

Comments

  • Anonymous
    February 05, 2006
    The other day, colleague Shawn Steele posted in his blog about the ISO 8601 Week of Year format in Microsoft...

  • Anonymous
    March 22, 2007
    The comment has been removed

  • Anonymous
    August 01, 2007
    Neat, and simple! But how would one go about this the other way? taking an ISO8601 week date into a .NET/CLR DateTime struct again. Regards,

  • Anonymous
    November 01, 2007
    hello sir/madam i have seen u r code and MS just im want to know which is right i want the number of week year for a particular year which code i wil use

  • Anonymous
    December 10, 2007
           /// <summary>        /// Get the last WeekNumber of the Current Year.        /// </summary>        /// <returns>        /// LastWeek Number        /// </returns>        public int LastWeekOfYear()        {            // Last day of year            DateTime dt = new DateTime(DateTime.Now.Year, 12, 31);            // Last week of year            int lastWeek = GetIso8601WeekOfYear(dt);            if (lastWeek == 1)            {                /// 31-12 is week 1 set the date a week back and get the last week.                dt = dt.AddDays(-7);                lastWeek = GetIso8601WeekOfYear(dt);            }            return lastWeek;        }

  • Anonymous
    October 13, 2008
    Wijnand : The last ISO week number of a year number is that of YYYY-12-28; no need to fumble. shawnste : The check in the code should be for at least 365.2425*400 days, say 150000 days.  MS VBS DatePart for WN gives two types of error, one being once in 400 years. My cited index links to a number of date pages. Jerome B : Right. An ISO WN routine needs to give also YN, and might as well give DN too.  It should be easy enough to write such without using GetWeekOfYear.  Algorithm is on my site, but I don't have or know Powershell/.NET. John : my site includes reverse algorithm, in JavaScript.

  • Anonymous
    September 21, 2009
    Can this method be used to get weeks where first day of week is sunday as well, or is it just to be used where monday is first day of week?

  • Anonymous
    September 22, 2009
    You could do the same thing with Sunday (if you wanted the week number to roll over to the next year so there weren't partial week numbers), but then it wouldn't be ISO 8601.  The ISO standard starts with Mondays.

  • Anonymous
    January 05, 2010
    This didn't work for me for friday, saturday and sundays at the start of this year. based on ISO-Week numbering (a tad different from ISO 8601, or so i'm told). i came up with the idea that calculating from thursdays should always work. i've modified your code to this:        protected void Selected(DateTime time)        {            System.Globalization.Calendar cal = CultureInfo.InvariantCulture.Calendar;            DateTime calculatedate = time;            DayOfWeek day = cal.GetDayOfWeek(time);            if (day == DayOfWeek.Monday) { calculatedate = calculatedate.AddDays(3); }            if (day == DayOfWeek.Tuesday) { calculatedate = calculatedate.AddDays(2); }            if (day == DayOfWeek.Wednesday) { calculatedate = calculatedate.AddDays(1); }            if (day == DayOfWeek.Friday) { calculatedate = calculatedate.AddDays(-1); }            if (day == DayOfWeek.Saturday) { calculatedate = calculatedate.AddDays(-2); }            if (day == DayOfWeek.Sunday) { calculatedate = calculatedate.AddDays(-3); }            // Return the week of our adjusted day            int i = cal.GetWeekOfYear(calculatedate, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);            string s = string.Format("Date : {0:d}t Day : {1}t Week Number: {2}",time,time.DayOfWeek.ToString(),i);            listBox1.Items.Add(s);        } in VB it would be:        Dim cal As System.Globalization.Calendar = CultureInfo.InvariantCulture.Calendar        Dim time As DateTime = Now        Dim day As DayOfWeek = time.DayOfWeek()        Dim CalculateDate As Date = time        If day = DayOfWeek.Monday Then CalculateDate = CalculateDate.AddDays(3)        If day = DayOfWeek.Tuesday Then CalculateDate = CalculateDate.AddDays(2)        If day = DayOfWeek.Wednesday Then CalculateDate = CalculateDate.AddDays(1)        If day = DayOfWeek.Friday Then CalculateDate = CalculateDate.AddDays(-1)        If day = DayOfWeek.Saturday Then CalculateDate = CalculateDate.AddDays(-2)        If day = DayOfWeek.Sunday Then CalculateDate = CalculateDate.AddDays(-3)        Dim i As Integer = cal.GetWeekOfYear(CalculateDate, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday)

    • Anonymous
      June 02, 2016
      Thanks Keno, this helps
  • Anonymous
    April 15, 2010
    //variant of code posted by Keno (Jan 06, 2010) //C#    private int weekOfYear(DateTime date, DayOfWeek firstDayOfWeek)    {        Calendar cal = CultureInfo.InvariantCulture.Calendar;        DayOfWeek day = cal.GetDayOfWeek(date);        date = date.AddDays(4 - ((int)day == 0 ? 7 : (int)day));        return cal.GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, firstDayOfWeek);    } // Provides for variable first day of week and smaller footprint

  • Anonymous
    December 11, 2012
    I haven't even looked at the workaround. My simple question is: Why? Why is there any difference? Who benefits from it? Why bother to slightly change an ISO standard?

  • Anonymous
    January 24, 2013
    It isn't an ISO standard rule, but people want the ISO standard rule, so this discusses ways to do that.

  • Anonymous
    April 10, 2013
    Adding a rule for ISO 8601 would be quite useful though.  Although given the signature of the GetWeekOfYear() method I suspect you'd need a method explicitly for ISO 8601, as the first day of the week isn't a user option, it's defined by the standard.

  • Anonymous
    May 01, 2015
    Jerome B: Your method that returns the year accounts for the last few days of the year being included in the first week of the next year, but not the other case of the first few days of a year being included in the last week of the previous year: ... else if (weekNumber == 1 && date.Month == 12) ++year;

  • Anonymous
    July 13, 2015
    To Cary -> as the year is extracted from the modified date (days + 3), the few days of last year included in week 1 will have their year adjusted already.

  • Anonymous
    November 08, 2016
    You could also:/// /// Converts a date to a week number./// ISO 8601 week 1 is the week that contains the first Thursday that year./// public static int ToIso8601Weeknumber(this DateTime date){ var thursday = date.AddDays(3 - date.DayOfWeek.DayOffset()); return (thursday.DayOfYear - 1) / 7 + 1;}/// /// Converts a week number to a date./// Note: Week 1 of a year may start in the previous year./// ISO 8601 week 1 is the week that contains the first Thursday that year, so/// if December 28 is a Monday, December 31 is a Thursday,/// and week 1 starts January 4./// If December 28 is a later day in the week, week 1 starts earlier./// If December 28 is a Sunday, it is in the same week as Thursday January 1./// public static DateTime FromIso8601Weeknumber(int weekNumber, int? year = null, DayOfWeek day = DayOfWeek.Monday){ var dec28 = new DateTime((year ?? DateTime.Today.Year) - 1, 12, 28); var monday = dec28.AddDays(7 * weekNumber - dec28.DayOfWeek.DayOffset()); return monday.AddDays(day.DayOffset());}/// /// Iso8601 weeks start on Monday. This returns 0 for Monday./// private static int DayOffset(this DayOfWeek weekDay){ return ((int)weekDay + 6) % 7;}

  • Anonymous
    August 16, 2017
    Shouldn't this be going the other way, with Microsoft actually making this a standard part of the build, rather than having to mangle a solution? The same with being forced into having Sunday as the start of the week.

    • Anonymous
      September 26, 2017
      FWIW, DateTimeFormatInfo has a FirstDayOfWeek.Yes, the ISO8601 calculation is problematic. "GetWeekOfYear()" cannot really change because that would break everyone that has used it the way it has been for over a decade. It might be reasonable to add a "GetIsoWeekOfYear()" or some such.