Функции Format или DatePart могут возвращать неправильное число недели для последнего понедельника в году

Предупреждение

Существует проблема с использованием этой функции. Последний понедельник в некоторых календарных годах может быть возвращен как неделя 53, когда это должна быть неделя 1. Дополнительные сведения и обходной путь см. в статье Функции формат или DatePart могут возвращать неправильный номер недели для последнего понедельника в году.

Симптомы

При использовании функции Format или DatePart для определения номера недели для дат с помощью следующего синтаксиса:

  • Format(AnyDate, "ww", vbMonday, vbFirstFourDays)
  • DatePart("ww", AnyDate, vbMonday, vbFirstFourDays)

последний понедельник в некоторых календарных годах возвращается как неделя 53, когда это должна быть неделя 1.

Причина

При определении номера недели даты в соответствии со стандартом ISO 8601 вызов базовой функции к файлу Oleaut32.dll по ошибке возвращает неделю 53 вместо недели 1 за последний понедельник в определенные годы.

Разрешение

Используйте определяемую пользователем функцию, чтобы вернуть номер недели на основе правил стандарта ISO 8601. Пример приведен в этой статье.

Дополнительные сведения

Стандарт ISO 8601 широко используется в Европе и включает в себя следующее:

ISO 8601 "Data elements and interchange formats - Information interchange   - Representation of dates and times"
ISO 8601 : 1988 (E) paragraph 3.17:
"week, calendar: A seven day period within a calendar year, starting on a Monday and identified by its ordinal number within the year; the first calendar week of the year is the one that includes the first Thursday of that year. In the Gregorian calendar, this is equivalent to the week which includes 4 January."

Это можно реализовать, применив следующие правила для календарных недель:

  • Год делится на 52 или 53 календарные недели.
  • Календарная неделя имеет 7 дней. Понедельник — день 1, воскресенье — день 7.
  • Первая календарная неделя года содержит не менее 4 дней.
  • Если год не завершается в воскресенье, его 1-3 последних дня относятся к первой календарной неделе следующего года или первые 1-3 дня следующего года относятся к последней календарной неделе текущего года.
  • Только год, начинающийся или завершающийся в четверг, имеет 53 календарные недели.

В Visual Basic и Visual Basic для приложений все функции даты, кроме функции DateSerial, поступают из вызовов файла Oleaut32.dll. Так как функции Format() и DatePart() могут возвращать номер календарной недели для заданной даты, эта ошибка затрагивает обе функции. Чтобы избежать этой проблемы, необходимо использовать альтернативный код, который предоставляется в этой статье.

Действия по воспроизведению поведения

  1. Откройте проект Visual Basic в приложении Office (ALT+F11).

  2. В меню Проект добавьте новый модуль.

  3. Вставьте следующий код в модуль:

    Option Explicit
    
    Public Function Test1()
    ' This code tests a "problem" date and the days around it
    Dim DateValue As Date
    Dim i As Integer
    
    Debug.Print "   Format function:"
    DateValue = #12/27/2003#
    For i = 1 To 4   ' examine the last 4 days of the year
     DateValue = DateAdd("d", 1, DateValue)
     Debug.Print "Date: " & DateValue & "   Day: " & _
     Format(DateValue, "ddd") & "   Week: " & _
     Format(DateValue, "ww", vbMonday, vbFirstFourDays)
    Next i
    End Function
    
    Public Function Test2()
    ' This code lists all "Problem" dates within a specified range
     Dim MyDate As Date
     Dim Years As Long
     Dim days As Long
     Dim woy1 As Long
     Dim woy2 As Long
     Dim ToPrint As String
    
     For Years = 1850 To 2050
     For days = 0 To 3
     MyDate = DateSerial(Years, 12, 28 + days)
     woy1 = Format(MyDate, "ww", vbMonday, vbFirstFourDays)
     woy2 = Format(MyDate, "ww", vbMonday, vbFirstFourDays)
     If woy2 > 52 Then
     If Format(MyDate + 7, "ww", vbMonday, vbFirstFourDays) = 2 Then _
     woy2 = 1
     End If
     If woy1 <> woy2 Then
     ToPrint = MyDate & String(13 - Len(CStr(MyDate)), " ")
     ToPrint = ToPrint & Format(MyDate, "dddd") & _
     String(10 - Len(Format(MyDate, "dddd")), " ")
     ToPrint = ToPrint & woy1 & String(5 - Len(CStr(woy1)), " ")
     ToPrint = ToPrint & woy2
     Debug.Print ToPrint
     End If
     Next days
    Next Years
    End Function
    
  4. Используйте (CTRL+G), чтобы открыть окно интерпретации, если оно еще не открыто.

  5. Введите ? Протестируйте 1 в окне Интерпретация и нажмите клавишу ВВОД, обратите внимание на следующие результаты в окне Интерпретация:

    Format function:
    Date: 12/28/03   Day: Sun   Week: 52
    Date: 12/29/03   Day: Mon   Week: 53
    Date: 12/30/03   Day: Tue   Week: 1
    Date: 12/31/03   Day: Wed   Week: 1
    

    В этом формате все недели начинаются с понедельника, поэтому 29.12.2003 следует считать началом недели 1, а не частью недели 53.

  6. Введите ? Протестируйте 2 в окне Интерпретация и нажмите клавишу ВВОД, чтобы просмотреть список дат в указанном диапазоне, где возникает эта проблема. Список включает дату, день недели (всегда понедельник), номер недели, возвращаемый форматом (53), а также номер недели, который он должен вернуть (1.) Например:

    12/29/1851   Monday    53   1
    12/31/1855   Monday    53   1
    12/30/1867   Monday    53   1
    12/29/1879   Monday    53   1
    12/31/1883   Monday    53   1
    12/30/1895   Monday    53   1
    ...
    

Временные решения

Если вы используете функции Format или DatePart, необходимо проверка возвращаемое значение. Если значение равно 53, запустите другой проверка и при необходимости принудим возврат значения 1. В этом примере кода демонстрируется один из способов сделать это:

Function WOY (MyDate As Date) As Integer   ' Week Of Year
  WOY = Format(MyDate, "ww", vbMonday, vbFirstFourDays)
  If WOY > 52 Then
    If Format(MyDate + 7, "ww", vbMonday, vbFirstFourDays) = 2 Then WOY = 1
  End If
End Function

Вы можете избежать использования этих функций для определения номера недели, написав код, реализующий описанные выше правила ISO 8601. В следующем примере показана функция замены, возвращаемая номер недели.

Пошаговый пример

  1. Откройте проект Visual Basic в приложении Office (ALT+F11).

  2. В меню Проект добавьте новый модуль.

  3. Вставьте следующий код в модуль:

    Option Explicit
    
    Function WeekNumber(InDate As Date) As Integer
     Dim DayNo As Integer
     Dim StartDays As Integer
     Dim StopDays As Integer
     Dim StartDay As Integer
     Dim StopDay As Integer
     Dim VNumber As Integer
     Dim ThurFlag As Boolean
    
     DayNo = Days(InDate)
     StartDay = Weekday(DateSerial(Year(InDate), 1, 1)) - 1
     StopDay = Weekday(DateSerial(Year(InDate), 12, 31)) - 1
     ' Number of days belonging to first calendar week
     StartDays = 7 - (StartDay - 1)
     ' Number of days belonging to last calendar week
     StopDays = 7 - (StopDay - 1)
     ' Test to see if the year will have 53 weeks or not
     If StartDay = 4 Or StopDay = 4 Then ThurFlag = True Else ThurFlag = False
     VNumber = (DayNo - StartDays - 4) / 7
     ' If first week has 4 or more days, it will be calendar week 1
     ' If first week has less than 4 days, it will belong to last year's
     ' last calendar week
     If StartDays >= 4 Then 
     WeekNumber = Fix(VNumber) + 2 
     Else 
     WeekNumber = Fix(VNumber) + 1
     End If
     ' Handle years whose last days will belong to coming year's first
     ' calendar week
     If WeekNumber > 52 And ThurFlag = False Then WeekNumber = 1
     ' Handle years whose first days will belong to the last year's 
     ' last calendar week
     If WeekNumber = 0 Then
     WeekNumber = WeekNumber(DateSerial(Year(InDate) - 1, 12, 31))
     End If
    End Function
    
    Function Days(DayNo As Date) As Integer
     Days = DayNo - DateSerial(Year(DayNo), 1, 0)
    End Function
    
    Public Function Test3()
     Dim DateValue As Date, i As Integer
    
     Debug.Print "   WeekNumber function:"
     DateValue = #12/27/2003#
     For i = 1 To 4   ' examine the last 4 days of the year
     DateValue = DateAdd("d", 1, DateValue)
     Debug.Print "Date: " & DateValue & "   Day: " & _
          Format(DateValue, "ddd") & "   Week: " & WeekNumber(DateValue)
     Next i
    End Function
    
  4. Используйте (CTRL+G), чтобы открыть окно интерпретации, если оно еще не открыто.

  5. Введите ? Test3 в окне Интерпретация и нажмите клавишу ВВОД, обратите внимание на следующие результаты в окне Интерпретация:

    WeekNumber function:
    Date: 12/28/03   Day: Sun   Week: 52
    Date: 12/29/03   Day: Mon   Week: 1
    Date: 12/30/03   Day: Tue   Week: 1
    Date: 12/31/03   Day: Wed   Week: 1
    

    Обратите внимание, что понедельник считается неделей 1, как и должно быть.