Udostępnij za pośrednictwem


Funkcje Format lub DatePart mogą zwracać nieprawidłowy numer tygodnia dla ostatniego poniedziałku w roku

Ostrzeżenie

Wystąpił problem z użyciem tej funkcji. Ostatni poniedziałek w niektórych latach kalendarzowych może zostać zwrócony jako tydzień 53, kiedy powinien to być tydzień 1. Aby uzyskać więcej informacji i obejść ten problem, zobacz Funkcje Format lub DatePart mogą zwracać nieprawidłowy numer tygodnia dla ostatniego poniedziałku w roku.

Objawy

Jeśli używasz funkcji Format lub DatePart, aby określić numer tygodnia dla dat przy użyciu następującej składni:

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

Ostatni poniedziałek w niektórych latach kalendarzowych jest zwracany jako tydzień 53, kiedy powinien być tydzień 1.

Przyczyna

Podczas określania numeru tygodnia daty zgodnie ze standardem ISO 8601, bazowe wywołanie funkcji do pliku Oleaut32.dll błędnie zwraca tydzień 53 zamiast tygodnia 1 w ostatni poniedziałek w niektórych latach.

Rezolucja

Użyj funkcji zdefiniowanej przez użytkownika, aby zwrócić numer tygodnia na podstawie reguł standardu ISO 8601. Przykład znajduje się w tym artykule.

Więcej informacji

Standard ISO 8601 jest szeroko stosowany w Europie i obejmuje:

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."

Można ją zaimplementować, stosując te reguły dla tygodni kalendarzowych:

  • Rok jest podzielony na 52 lub 53 tygodnie kalendarzowe.
  • Tydzień kalendarzowy ma siedem dni. Poniedziałek to dzień 1, a niedziela to dzień 7.
  • Pierwszy tydzień kalendarzowy roku to ten zawierający co najmniej cztery dni.
  • Jeśli rok nie zakończy się w niedzielę, to albo jego 1-3 ostatnich dni będzie należało do pierwszego tygodnia kalendarzowego następnego roku, albo pierwsze 1-3 dni następnego roku będą należały do ostatniego tygodnia kalendarzowego bieżącego roku.
  • Tylko rok rozpoczynający się lub kończący w czwartek ma 53 tygodnie kalendarzowe.

W programach Visual Basic i Visual Basic for Applications wszystkie funkcje daty, z wyjątkiem funkcji DateSerial, pochodzą z wywołań do pliku Oleaut32.dll. Ponieważ funkcje Format() i DatePart() mogą zwracać numer tygodnia kalendarzowego dla danej daty, oba te elementy mają wpływ na tę usterkę. Aby uniknąć tego problemu, należy użyć alternatywnego kodu zawartego w tym artykule.

Kroki odtwarzania zachowania

  1. Otwórz projekt Visual Basic w aplikacji pakietu Office (Alt + F11).

  2. Z menu Project (Projekt ) dodaj nowy moduł.

  3. Wklej następujący kod do modułu:

    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. Użyj (Ctrl + G), aby otworzyć okno natychmiastowe, jeśli jeszcze nie jest otwarte.

  5. Wpisz ? Test1 w oknie Natychmiastowy i naciśnij Enter, zwróć uwagę na następujące wyniki w oknie Natychmiastowy:

    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
    

    W tym formacie wszystkie tygodnie zaczynają się od poniedziałku, tak aby 29 grudnia 2003 r. należy uznać za początek tygodnia 1, a nie część tygodnia 53.

  6. Wpisz ?Test2 w oknie Natychmiast i naciśnij Enter, aby wyświetlić listę dat w określonym zakresie, które napotykają ten problem. Lista zawiera datę, dzień tygodnia (zawsze poniedziałek), numer tygodnia, zwrócony przez funkcję Format (53), i numer tygodnia, który powinien zwrócić (1). Na przykład:

    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
    ...
    

Rozwiązania alternatywne

Jeśli używasz funkcji Format lub DatePart, musisz sprawdzić wartość zwracaną. Gdy wartość wynosi 53, uruchom kolejną kontrolę i w razie potrzeby wymuś zwrot wartości 1. Ten przykładowy kod przedstawia jeden ze sposobów, aby to zrobić:

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

Aby określić numer tygodnia, można uniknąć używania tych funkcji, pisząc kod implementujący opisane powyżej reguły ISO 8601. W poniższym przykładzie pokazano funkcję zamiany, która zwraca numer tygodnia.

Przykład krok po kroku

  1. Otwórz projekt Visual Basic w aplikacji pakietu Office (Alt + F11).

  2. Z menu Project (Projekt ) dodaj nowy moduł.

  3. Wklej następujący kod do modułu:

    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. Użyj (Ctrl + G), aby otworzyć okno natychmiastowe, jeśli jeszcze nie jest otwarte.

  5. Wpisz ? Test3 w oknie Natychmiastowy i naciśnij Enter, zwróć uwagę na następujące wyniki w oknie Natychmiastowy:

    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
    

    Poniedziałek jest uważany za tydzień 1, jak to powinno być.