ActiveGantt Gantt Chart / Scheduler Components for Windows Forms, ASP.NET, WPF, Silverlight and ActiveX enabled development systems. Visual Basic.NET, C# and C++.
A function to calculate the number of the week, ISO 8601, VB.Net, C# and MFC VC++

Tuesday, June 11, 2013

A lot of functions are posted online, with varying degrees of accuracy, that calculate the number of the week of the year. Having looked around and not finding any simple, yet general, algorithm that would fit our needs, we decided to develop our own.

As a starting point we looked at the Calendar.GetWeekOfYear function that is included in the .NET framework and decided it was a good fit. This function takes three arguments: the date, a rule that controls the first week of the year and the starting day of the week.

Visual Basic .NET and C# do not need a function that calculates the number of the week because you can use the Calendar.GetWeekOfYear function that is already included. But if you intend on having this function in another programming language (Java, SQL, etc.) you might find the source code useful and easier to port. Also we included it if you want to avoid using System.Globalization or further customize the functions.

You will have to define two enumerations, one for the days of the week:


//C++ code

typedef enum E_WEEKDAY
{
    WD_SUNDAY = 0,
    WD_MONDAY = 1,
    WD_TUESDAY = 2,
    WD_WEDNESDAY = 3,
    WD_THURSDAY = 4,
    WD_FRIDAY = 5,
    WD_SATURDAY = 6
}E_WEEKDAY;

//C# code

    public enum E_WEEKDAY
    {
        WD_SUNDAY = 0,
        WD_MONDAY = 1,
        WD_TUESDAY = 2,
        WD_WEDNESDAY = 3,
        WD_THURSDAY = 4,
        WD_FRIDAY = 5,
        WD_SATURDAY = 6
    }

'Visual Basic .NET code

Public Enum E_WEEKDAY
    WD_SUNDAY = 0
    WD_MONDAY = 1
    WD_TUESDAY = 2
    WD_WEDNESDAY = 3
    WD_THURSDAY = 4
    WD_FRIDAY = 5
    WD_SATURDAY = 6
End Enum

And another for the rules that govern the starting week of the year:


//C++ code

typedef enum E_CALENDARWEEKRULES
{
    CWR_FIRSTDAY = 0,
    CWR_FIRSTFULLWEEK = 1,
    CWR_FIRSTFOURDAYWEEK = 2,
}E_CALENDARWEEKRULES;

//C# code

    public enum E_CALENDARWEEKRULES
    {
        CWR_FIRSTDAY = 0,
        CWR_FIRSTFULLWEEK = 1,
        CWR_FIRSTFOURDAYWEEK = 2
    }

'Visual Basic .NET code

Public Enum E_CALENDARWEEKRULES
    CWR_FIRSTDAY = 0
    CWR_FIRSTFULLWEEK = 1
    CWR_FIRSTFOURDAYWEEK = 2
End Enum

These two enumerations map directly to the System.DayOfWeek and System.Globalization.CalendarWeekRule enumerations that are contained in the .NET framework.

Now according to the .NET framework documentation the definition for the CalendarWeekRule enumeration is the following:

Member name Description
FirstDay Indicates that the first week of the year starts on the first day of the year and ends before the following designated first day of the week. The value is 0.
FirstFullWeek Indicates that the first week of the year begins on the first occurrence of the designated first day of the week on or after the first day of the year. The value is 1.
FirstFourDayWeek Indicates that the first week of the year is the first week with four or more days before the designated first day of the week. The value is 2.

The code for calculating the number of the week is:


//C++ code

int GetWeekOfYear(COleDateTime time, int rule, int firstDayOfWeek)
{
    int lReturn = 0;
    COleDateTime dtBase(time.GetYear(), 1, 1, 0, 0, 0);
    COleDateTime dtBuff;
    int lDay = 0;
    int lWeek = 0;
    switch (rule) 
    {
    case CWR_FIRSTDAY:
        for (lDay = 0; lDay <= 6; lDay++) 
        {
            COleDateTimeSpan oSpan(lDay, 0, 0, 0);
            dtBuff = dtBase + oSpan;
            if ((dtBuff.GetDayOfWeek() - 1) == firstDayOfWeek) 
            {
                break;
            }
        }
        if ((dtBase.GetDayOfWeek() - 1) == firstDayOfWeek) 
        {
            lReturn = (int)floor(((double)time.GetDayOfYear() - (double)dtBuff.GetDayOfYear()) / 7.0) + 1;
        } 
        else 
        {
            lReturn = (int)floor(((double)time.GetDayOfYear() - (double)dtBuff.GetDayOfYear()) / 7.0) + 2;
        }
        break;
    case CWR_FIRSTFULLWEEK:
        for (lDay = 0; lDay <= 6; lDay++) 
        {
            COleDateTimeSpan oSpan(lDay, 0, 0, 0);
            dtBuff = dtBase + oSpan;
            if ((dtBuff.GetDayOfWeek() - 1) == firstDayOfWeek) 
            {
                break;
            }
        }
        lWeek = (int)floor(((double)time.GetDayOfYear() - (double)dtBuff.GetDayOfYear()) / 7) + 1;
        if (lWeek == 0) 
        {
            COleDateTime dtParam(time.GetYear() - 1, 12, 31, 0, 0, 0);
            lReturn = GetWeekOfYear(dtParam, CWR_FIRSTFULLWEEK, firstDayOfWeek);
        } else 
        {
            lReturn = lWeek;
        }
        break;
    case CWR_FIRSTFOURDAYWEEK:
        for (lDay = 0; lDay <= 12; lDay++) 
        {
            COleDateTimeSpan oSpan(lDay, 0, 0, 0);
            dtBuff = dtBase + oSpan;
            if ((dtBuff.GetDayOfWeek() - 1) == firstDayOfWeek && dtBuff.GetDayOfYear() >= 5) 
            {
                break;
            }
        }
        lWeek = (int)floor(((double)time.GetDayOfYear() - (double)dtBuff.GetDayOfYear()) / 7.0) + 2;
        if (lWeek == 0) 
        {
            COleDateTime dtParam(time.GetYear() - 1, 12, 31, 0, 0, 0);
            lReturn = GetWeekOfYear(dtParam, CWR_FIRSTFOURDAYWEEK, firstDayOfWeek);
        } 
        else 
        {
            lReturn = lWeek;
        }
        break;
    }
    return lReturn;
}


//C# code

        private int GetWeekOfYear(System.DateTime time, E_CALENDARWEEKRULES rule, E_WEEKDAY firstDayOfWeek)
        {
            int lReturn = 0;
            System.DateTime dtBase = new System.DateTime(time.Year, 1, 1);
            System.DateTime dtBuff = default(System.DateTime);
            int lDay = 0;
            int lWeek = 0;
            switch (rule)
            {
                case E_CALENDARWEEKRULES.CWR_FIRSTDAY:
                    for (lDay = 0; lDay <= 6; lDay++)
                    {
                        dtBuff = dtBase.AddDays(lDay);
                        if ((E_WEEKDAY)dtBuff.DayOfWeek == firstDayOfWeek)
                        {
                            break; 
                        }
                    }

                    if ((E_WEEKDAY)dtBase.DayOfWeek == firstDayOfWeek)
                    {
                        lReturn = (int)System.Math.Floor(((double)time.DayOfYear - (double)dtBuff.DayOfYear) / 7D) + 1;
                    }
                    else
                    {
                        lReturn = (int)System.Math.Floor(((double)time.DayOfYear - (double)dtBuff.DayOfYear) / 7D) + 2;
                    }
                    break;
                case E_CALENDARWEEKRULES.CWR_FIRSTFULLWEEK:
                    for (lDay = 0; lDay <= 6; lDay++)
                    {
                        dtBuff = dtBase.AddDays(lDay);
                        if ((E_WEEKDAY)dtBuff.DayOfWeek == firstDayOfWeek)
                        {
                            break;
                        }
                    }

                    lWeek = (int)System.Math.Floor(((double)time.DayOfYear - (double)dtBuff.DayOfYear) / 7D) + 1;
                    if (lWeek == 0)
                    {
                        lReturn = GetWeekOfYear(new System.DateTime(time.Year - 1, 12, 31), E_CALENDARWEEKRULES.CWR_FIRSTFULLWEEK, firstDayOfWeek);
                    }
                    else
                    {
                        lReturn = lWeek;
                    }
                    break;
                case E_CALENDARWEEKRULES.CWR_FIRSTFOURDAYWEEK:
                    for (lDay = 0; lDay <= 12; lDay++)
                    {
                        dtBuff = dtBase.AddDays(lDay);
                        if ((E_WEEKDAY)dtBuff.DayOfWeek == firstDayOfWeek & dtBuff.DayOfYear >= 5)
                        {
                            break;
                        }
                    }

                    lWeek = (int)System.Math.Floor(((double)time.DayOfYear - (double)dtBuff.DayOfYear) / 7D) + 2;
                    if (lWeek == 0)
                    {
                        lReturn = GetWeekOfYear(new System.DateTime(time.Year - 1, 12, 31), E_CALENDARWEEKRULES.CWR_FIRSTFOURDAYWEEK, firstDayOfWeek);
                    }
                    else
                    {
                        lReturn = lWeek;
                    }
                    break;
            }
            return lReturn;
        }


'Visual Basic .NET code

    Private Function GetWeekOfYear(ByVal time As System.DateTime, ByVal rule As E_CALENDARWEEKRULES, ByVal firstDayOfWeek As E_WEEKDAY) As Integer
        Dim lReturn As Integer = 0
        Dim dtBase As System.DateTime = New System.DateTime(time.Year(), 1, 1)
        Dim dtBuff As System.DateTime
        Dim lDay As Integer
        Dim lWeek As Integer = 0
        Select Case rule
            Case E_CALENDARWEEKRULES.CWR_FIRSTDAY
                For lDay = 0 To 6
                    dtBuff = dtBase.AddDays(lDay)
                    If dtBuff.DayOfWeek() = firstDayOfWeek Then
                        Exit For
                    End If
                Next
                If dtBase.DayOfWeek = firstDayOfWeek Then
                    lReturn = System.Math.Floor((time.DayOfYear() - dtBuff.DayOfYear()) / 7) + 1
                Else
                    lReturn = System.Math.Floor((time.DayOfYear() - dtBuff.DayOfYear()) / 7) + 2
                End If
            Case E_CALENDARWEEKRULES.CWR_FIRSTFULLWEEK
                For lDay = 0 To 6
                    dtBuff = dtBase.AddDays(lDay)
                    If dtBuff.DayOfWeek() = firstDayOfWeek Then
                        Exit For
                    End If
                Next
                lWeek = System.Math.Floor((time.DayOfYear() - dtBuff.DayOfYear()) / 7) + 1
                If lWeek = 0 Then
                    lReturn = GetWeekOfYear(New System.DateTime(time.Year - 1, 12, 31), E_CALENDARWEEKRULES.CWR_FIRSTFULLWEEK, firstDayOfWeek)
                Else
                    lReturn = lWeek
                End If
            Case E_CALENDARWEEKRULES.CWR_FIRSTFOURDAYWEEK
                For lDay = 0 To 12
                    dtBuff = dtBase.AddDays(lDay)
                    If dtBuff.DayOfWeek() = firstDayOfWeek And dtBuff.DayOfYear >= 5 Then
                        Exit For
                    End If
                Next
                lWeek = System.Math.Floor((time.DayOfYear() - dtBuff.DayOfYear()) / 7) + 2
                If lWeek = 0 Then
                    lReturn = GetWeekOfYear(New System.DateTime(time.Year - 1, 12, 31), E_CALENDARWEEKRULES.CWR_FIRSTFOURDAYWEEK, firstDayOfWeek)
                Else
                    lReturn = lWeek
                End If
        End Select
        Return lReturn
    End Function

This code has been tested extensively and produces results that are consistent with the Calendar.GetWeekOfYear function.

In MFC the COleDateTime::GetDayOfWeek() function defines Sunday as 1, and that is why we have to subtract 1 when using this function.

Some programmers use the Calendar.GetWeekOfYear function with a FirstFourDayWeek rule and set the starting date to Monday and this does not exactly map to ISO 8601 standards.

But there is a simple workaround and it is contained in the following method:


//C++ code

int GetIso8601WeekOfYear(COleDateTime time)
{
    int yDay = time.GetDayOfWeek() - 1;
    COleDateTime dtBuff = time;
    if (yDay >= WD_MONDAY && yDay <= WD_WEDNESDAY)
    {
        COleDateTimeSpan oSpan(3, 0, 0, 0);
        dtBuff = time + oSpan;
    }
    return GetWeekOfYear(dtBuff, CWR_FIRSTFOURDAYWEEK, WD_MONDAY);
}

//C# code

        private int GetIso8601WeekOfYear(System.DateTime time)
        {
            E_WEEKDAY yDay = (E_WEEKDAY)time.DayOfWeek;
            System.DateTime dtBuff = time;
            if (yDay >= E_WEEKDAY.WD_MONDAY & yDay <= E_WEEKDAY.WD_WEDNESDAY)
            {
                dtBuff = dtBuff.AddDays(3);
            }
            return GetWeekOfYear(dtBuff, E_CALENDARWEEKRULES.CWR_FIRSTFOURDAYWEEK, E_WEEKDAY.WD_MONDAY);
        }

'Visual Basic .NET code

    Private Function GetIso8601WeekOfYear(ByVal time As System.DateTime) As Integer
        Dim yDay As E_WEEKDAY = time.DayOfWeek
        Dim dtBuff As System.DateTime = time
        If yDay >= E_WEEKDAY.WD_MONDAY And yDay <= E_WEEKDAY.WD_WEDNESDAY Then
            dtBuff = dtBuff.AddDays(3)
        End If
        Return GetWeekOfYear(dtBuff, E_CALENDARWEEKRULES.CWR_FIRSTFOURDAYWEEK, E_WEEKDAY.WD_MONDAY)
    End Function

All trademarks are property of their respective holders, and are only used to directly describe the products and services being provided. Their use in no way indicates any relationship or endorsement between The Source Code Store LLC and the holders of said trademarks.