Format 関数または DatePart 関数は、昨年の最後の月曜日に間違った週番号を返す可能性があります

警告

この関数の使用に問題があります。 一部の暦年の最後の月曜日は、週 1 である必要がある週 53 として返すことができます。 詳細と回避策については、「 Format または DatePart 関数は、昨年の最後の月曜日に間違った週番号を返す可能性がある」を参照してください。

現象

Format 関数または DatePart 関数を使用して、次の構文を使用して日付の週番号を決定する場合:

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

一部の暦年の最後の月曜日は、週 1 である必要がある週 53 として返されます。

原因

ISO 8601 標準に従って日付の週番号を決定する場合、Oleaut32.dll ファイルの基になる関数呼び出しは、特定の年の最後の月曜日の週 1 ではなく、誤って 53 週目を返します。

解決方法

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 日目です。
  • 1 年の最初のカレンダー週は、少なくとも 4 日間を含む週です。
  • 1 年が日曜日に終わっていない場合は、その 1 から 3 の最後の日が来年の最初の暦週に属するか、来年の最初の 1 から 3 日が現在の年の最後の暦週に属します。
  • 木曜日の開始または終了の年は、53 暦週のみです。

Visual Basic と Visual Basic for Applications では、DateSerial 関数を除くすべての日付機能は、Oleaut32.dll ファイルの呼び出しから取得されます。 Format() 関数と DatePart() 関数の両方が特定の日付のカレンダー週番号を返すことができるため、どちらもこのバグの影響を受けます。 この問題を回避するには、この記事で提供される代替コードを使用する必要があります。

動作を再現する手順

  1. Office アプリケーション内で Visual Basic プロジェクトを開きます (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 in the Immediate window and hit Enter, note the following results in the Immediate window:

    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
    

    この形式では、すべての週が月曜日から始まるので、2003 年 12 月 29 日は第 53 週の一部ではなく、第 1 週の開始と見なされます。

  6. ?[ イミディエイト] ウィンドウで Test2 をクリックし、Enter キーを押して、この問題が発生した指定された範囲内の日付の一覧を表示します。 リストには、日付、週の日 (常に月曜日)、書式 (53) で返される Week #、および返される週番号 (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 を返します。 このコード サンプルでは、これを行う 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. Office アプリケーション内で Visual Basic プロジェクトを開きます (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 in the Immediate window and hit Enter, note the following results in the Immediate window:

    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 と見なされます。