Неверный номер недели ISO8601 с использованием DatePart()

У меня есть следующий Sub в моем классе. У меня нет ошибок, но результаты не соответствуют стандартам ISO-8601

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

Некоторый пример значений, возвращаемых из m_CurrentWeekNoYear для данной даты dt

  • 20/07/2015 -> 30-2015 правильно
  • 09/03/2015 -> 11-2015 правильно
  • 29/12/2014 -> 53-2014 неверно должно быть 01-2015... с использованием 01.02.2015 также дали 53-2014
  • 01.05.2015 -> 01-2015 неверно должно быть 02-2015... с помощью 01.09.2015 также дали 01-2015
  • 30/12/2013 -> 53-2013 неверно должно быть 01-2014... с использованием 01.04.2014 также дал 53-2013
  • 01.06.2014 -> 02-2014 правильно

Затем, когда я добрался до года, у которого фактически есть 53 недели, это работает.

  • 28/12/2009 -> 53-2009 правильно
  • 01.04.2010 -> 01-2010 правильно

Есть идеи, где я ошибся?

2 ответа

Решение

Использование DateAdd и DatePart общеизвестно плохо в такого рода вещах, но проблема в том, что существует (ошибка) несоответствие в способе.NET вычисляет номер недели.

Для получения дополнительной информации см. Эту страницу в формате ISO 8601 "Неделя года" в Microsoft .Net.

"В частности, ISO 8601 всегда имеет 7-дневные недели. Если первая неполная неделя года не содержит четверга, то она считается последней неделей предыдущего года. Аналогично, если последняя неделя предыдущего года не имеет содержать четверг, тогда его [sic] обрабатывается как первая неделя следующего года. GetWeekOfYear() имеет первое поведение, но не второе "

Вот некоторый код, который я написал, чтобы получить номер недели ISO 8601 на основе определения:

"Номер недели в соответствии со стандартом ISO-8601, недели, начинающиеся с понедельника. Первая неделя года - это неделя, в которой содержится первый четверг этого года (=" Первая 4-дневная неделя "). Самый высокий номер недели в году - либо 52, либо 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

Он использует следующие более общие методы, чтобы найти неделю года, используя специфику стандарта ISO:

'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

Этот код использует этот метод расширения, который является быстрым поиском, чтобы найти следующий день недели:

<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

Я попробовал ваши тестовые примеры с этим и получил следующее:

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

Вот простой обход ошибки в функции DatePart:

Week = DatePart("ww", myDate, vbMonday, vbFirstFourDays)
If Week = 53 And DatePart("ww", DateAdd("d", 7, myDate), vbMonday, vbFirstFourDays) = 2 Then
   Week = 1
End If
Другие вопросы по тегам