Поделиться через


Функции 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 календарных недель.
  • Календарная неделя имеет семь дней. Понедельник день 1 и воскресенье день 7.
  • Первая календарная неделя года — это одна, содержащая по крайней мере четыре дня.
  • Если год не заключен в воскресенье, то его 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. Введите ?Test1 в окне быстрого просмотра и нажмите клавишу ВВОД, обратите внимание на следующие результаты в окне быстрого просмотра:

    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 декабря 2003 года следует считать началом недели 1, а не частью недели 53.

  6. Введите ?Test2 в окно непосредственного выполнения и нажмите клавишу ВВОД, чтобы просмотреть список дат в указанном диапазоне, которые сталкиваются с этой проблемой. Список включает дату, день недели (всегда понедельник), номер недели, возвращаемый функцией форматирования (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 в окне Immediate и нажмите клавишу ВВОД, обратите внимание на следующие результаты в окне Immediate.

    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
    

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