格式或 DatePart 函数可能返回错误的周数给年中的最后一个星期一

警告

使用此函数时出现问题。 在某些日历年中,最后一个星期一可能被错误地视为第 53 周,而实际上应该是第 1 周。 有关详细信息和解决方法,请参阅 Format 或 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 for Applications 中,除 DateSerial 函数之外的所有日期功能都来自对 Oleaut32.dll 文件的调用。 由于 Format() 和 DatePart() 函数都可以返回给定日期的日历周号,因此两者都受此 bug 的影响。 若要避免此问题,必须使用本文提供的替代代码。

重现行为的步骤

  1. 在 Office 应用程序(Alt + F11)中打开 Visual Basic 项目。

  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
    

    使用此格式,所有周从星期一开始,以便 2003 年 12 月 29 日被视为第 1 周的开始,而不是第 53 周的一部分。

  6. 在“立即”窗口中输入Test2,然后按 Enter 键来查看指定范围内遭遇此问题的日期列表。 该列表包括日期、周日(始终为星期一)、格式返回的周 #(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. 在 Office 应用程序(Alt + F11)中打开 Visual Basic 项目。

  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 并按 Enter,然后记下“立即窗口”中的以下结果:

    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 周,这才是合理的。