Format 或 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 个日历周。
- 日历周有 7 天。 星期一为第 1 天,星期日为第 7 天。
- 一年的第一个日历周是包含至少四天的日历周。
- 如果一年未在星期日结束,则其最后 1-3 天属于下一年的第一个日历周,或者明年的前 1-3 天属于当前年份的最后一个日历周。
- 从星期四开始或结束的一年只有 53 个日历周。
在 Visual Basic 和 Visual Basic for Applications 中,除 DateSerial 函数之外的所有日期功能都来自对 Oleaut32.dll 文件的调用。 由于 Format () 和 DatePart () 函数都可以返回给定日期的日历周编号,因此两者都受此 bug 的影响。 若要避免此问题,必须使用本文提供的替代代码。
重现行为的步骤
在 Office 应用程序中打开 Visual Basic 项目, (Alt + F11) 。
从“ 项目 ”菜单中,添加新模块。
将以下代码粘贴到模块中:
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
使用 (Ctrl + G) 打开“即时窗口”(如果尚未打开)。
键入 ?“ 立即”窗口中的 Test1 并按 Enter,请注意“即时”窗口中的以下结果:
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 周的一部分。
键入 ?“ 立即”窗口中的 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 规则的代码来避免使用这些函数来确定周数。 以下示例演示用于返回周数的替换函数。
分步示例
在 Office 应用程序中打开 Visual Basic 项目, (Alt + F11) 。
从“ 项目 ”菜单中,添加新模块。
将以下代码粘贴到模块中:
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
使用 (Ctrl + G) 打开“即时窗口”(如果尚未打开)。
键入 ?在 “立即”窗口中 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 周。