Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Incorrect ISO8601 Week Number using DatePart()

I have the following Sub within my class. I have no errors but the results do not match ISO-8601 standards

Private Sub calculateAllProperties(ByVal dt As Date)

    Select Case dt.DayOfWeek

        Case DayOfWeek.Monday

            m_CurrentWeekStartDate = dt

        Case DayOfWeek.Tuesday

            m_CurrentWeekStartDate = DateAdd(DateInterval.Day, -1, dt)

        Case DayOfWeek.Wednesday

            m_CurrentWeekStartDate = DateAdd(DateInterval.Day, -2, dt)

        Case DayOfWeek.Thursday

            m_CurrentWeekStartDate = DateAdd(DateInterval.Day, -3, dt)

        Case DayOfWeek.Friday

            m_CurrentWeekStartDate = DateAdd(DateInterval.Day, -4, dt)

        Case DayOfWeek.Saturday

            m_CurrentWeekStartDate = DateAdd(DateInterval.Day, -5, dt)

        Case DayOfWeek.Sunday

            m_CurrentWeekStartDate = DateAdd(DateInterval.Day, -6, dt)

    End Select

    'Now we have our start point of m_CurrentWeekStartDate we can calculate all other properties.

    m_CurrentWeekStartYear = DatePart(DateInterval.Year, m_CurrentWeekStartDate)

    m_CurrentWeekNo = DatePart(DateInterval.WeekOfYear, m_CurrentWeekStartDate, Microsoft.VisualBasic.FirstDayOfWeek.Monday, FirstWeekOfYear.FirstFourDays)

    m_CurrentWeekNoYear = CurrentWeekNo.ToString("D2") & "-" & CurrentWeekStartYear.ToString
    m_CurrentYearWeekNo = CurrentWeekStartYear.ToString & "-" & CurrentWeekNo.ToString("D2")


    m_PreviousWeekStartDate = DateAdd(DateInterval.Day, -7, m_CurrentWeekStartDate)

    m_PreviousWeekStartYear = DatePart(DateInterval.Year, m_PreviousWeekStartDate)

    m_PreviousWeekNo = DatePart(DateInterval.WeekOfYear, m_PreviousWeekStartDate, Microsoft.VisualBasic.FirstDayOfWeek.Monday, FirstWeekOfYear.FirstFourDays)

    m_PreviousWeekNoYear = PreviousWeekNo.ToString("D2") & "-" & PreviousWeekStartYear.ToString
    m_PreviousYearWeekNo = PreviousWeekStartYear.ToString & "-" & PreviousWeekNo.ToString("D2")

End Sub

Some example of the values returned from m_CurrentWeekNoYear for a given date dt

  • 20/07/2015 -> 30-2015 correct
  • 09/03/2015 -> 11-2015 correct
  • 29/12/2014 -> 53-2014 incorrect should be 01-2015... using 02/01/2015 also gave 53-2014
  • 05/01/2015 -> 01-2015 incorrect should be 02-2015... using 09/01/2015 also gave 01-2015
  • 30/12/2013 -> 53-2013 incorrect should be 01-2014... using 04/01/2014 also gave 53-2013
  • 06/01/2014 -> 02-2014 correct

Then when I got to a year that actually has 53 weeks it works.

  • 28/12/2009 -> 53-2009 correct
  • 04/01/2010 -> 01-2010 correct

Any ideas where I've gone wrong?

like image 751
Mych Avatar asked Sep 28 '22 06:09

Mych


2 Answers

Using DateAdd and DatePart is notoriously bad at this sort of thing, but the issue is that there is a (bug) inconsistency in the way .NET calculates the week number.

See this page for more info ISO 8601 Week of Year format in Microsoft .Net

"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[sic] treated like the first week of the next year. GetWeekOfYear() has the first behavior, but not the second"

This is some code I wrote to get the ISO 8601 week number based on the definition:

"Week number according to the ISO-8601 standard, weeks starting on Monday. The first week of the year is the week that contains that year's first Thursday (='First 4-day week'). The highest week number in a year is either 52 or 53."

''' <summary>
''' Finds the ISO 8601 Week Number based on a given date
''' </summary>
''' <param name="dateValue"></param>
''' <returns></returns>
''' <remarks>ISO 8601 Specifies Monday as the First Day of the week and week one defined as the First Four Day Week</remarks>
Public Shared Function GetIso8601WeekOfYear(ByVal dateValue As DateTime) As Integer
    Return GetWeekOfYear(dateValue, DayOfWeek.Sunday, CalendarWeekRule.FirstFourDayWeek)
End Function

It uses the following more generic methods to find the week of the year using the specifics of the ISO standard:

'Need a calendar - Culture's irrelevent since we specify start day of week
Private Shared cal As Calendar = CultureInfo.InvariantCulture.Calendar


''' <summary>
''' Returns the week number of the year based on the Last Day of the Week and the Week One Rule
''' </summary>
''' <param name="dateValue">The date to find the week number for</param>
''' <param name="lastDayOfWeek">The last day of the week</param>
''' <param name="rule">The Definition of Week One</param>
''' <returns>An integer specifying the week number in the year</returns>
''' <remarks></remarks>
Public Shared Function GetWeekOfYear(ByVal dateValue As DateTime, ByVal lastDayOfWeek As DayOfWeek, ByVal rule As CalendarWeekRule) As Integer
    'There is a bug in the .NET framework where some dates at the end of the year return the incorrect week number so to find the correct value we need to cheat.  
    'Find the DayOfWeek that represents the first day based on the last day
    Dim firstDayOfWeek As DayOfWeek = lastDayOfWeek.Increment
    'Find the date of the last day of the week, this ensures that we get the correct week number value
    dateValue = GetWeekendingDate(dateValue, lastDayOfWeek)
    'Return the value of the week for the last day of the week
    Return cal.GetWeekOfYear(dateValue, rule, firstDayOfWeek)
End Function


''' <summary>
''' Finds the week ending date for a specified date value
''' </summary>
''' <param name="dateValue">The date to find the week ending date for</param>
''' <param name="lastDayOfWeek">The last day of the week</param>
''' <returns>A date value that is the last day of the week that contains the specified dateValue</returns>
''' <remarks></remarks>
Public Shared Function GetWeekendingDate(ByVal dateValue As DateTime, ByVal lastDayOfWeek As DayOfWeek) As DateTime
    'Find out how many days difference from the date we are testing to the end of the week
    Dim dayOffset As Integer = lastDayOfWeek - cal.GetDayOfWeek(dateValue)
    If dayOffset < 0 Then dayOffset += 7
    'Add days to the test date so that it is the last day of the week
    Return dateValue.AddDays(dayOffset)
End Function

This code uses this extension method which is a quick lookup to find the next day of the week:

<Extension()>
Public Function Increment(ByVal aDay As DayOfWeek) As DayOfWeek
    Select Case aDay
        Case DayOfWeek.Sunday : Return DayOfWeek.Monday
        Case DayOfWeek.Monday : Return DayOfWeek.Tuesday
        Case DayOfWeek.Tuesday : Return DayOfWeek.Wednesday
        Case DayOfWeek.Wednesday : Return DayOfWeek.Thursday
        Case DayOfWeek.Thursday : Return DayOfWeek.Friday
        Case DayOfWeek.Friday : Return DayOfWeek.Saturday
        Case DayOfWeek.Saturday : Return DayOfWeek.Sunday
        Case Else : Return Nothing
    End Select
End Function

I tried your test cases with this and got the following:

Debug.WriteLine(GetIso8601WeekOfYear(Date.Parse("20/07/2015"))) '30
Debug.WriteLine(GetIso8601WeekOfYear(Date.Parse("09/03/2015"))) '11
Debug.WriteLine(GetIso8601WeekOfYear(Date.Parse("29/12/2014"))) '1
Debug.WriteLine(GetIso8601WeekOfYear(Date.Parse("05/01/2015"))) '2
Debug.WriteLine(GetIso8601WeekOfYear(Date.Parse("30/12/2013"))) '1
Debug.WriteLine(GetIso8601WeekOfYear(Date.Parse("06/01/2014"))) '2
like image 134
Matt Wilko Avatar answered Oct 03 '22 08:10

Matt Wilko


Here is a simple workaround for the error in the DatePart function:

Week = DatePart("ww", myDate, vbMonday, vbFirstFourDays)
If Week = 53 And DatePart("ww", DateAdd("d", 7, myDate), vbMonday, vbFirstFourDays) = 2 Then
   Week = 1
End If
like image 26
FrankB Avatar answered Oct 03 '22 06:10

FrankB