BUG: Les fonctions Format et DatePart peuvent renvoyer un numéro de semaine erroné pour le dernier lundi de l'année

Symptômes

Lorsque vous utilisez la fonction Format ou DatePart pour déterminer le numéro de semaine d'une date avec la syntaxe suivante :
Format(AnyDate, "ww", vbMonday, vbFirstFourDays)

DatePart("ww", AnyDate, vbMonday, vbFirstFourDays)
certaines années, la semaine 53 est renvoyée pour le dernier lundi alors que ce devrait être la semaine 1.

Cause

Lors de la détermination du numéro de semaine d'une date selon la norme ISO 8601, l'appel de la fonction sous-jacente au fichier Oleaut32.dll renvoie par erreur la semaine 53 au lieu de la semaine 1 pour le dernier lundi de certaines années.

Résolution

Utilisez une fonction définie par l'utilisateur qui renverra le numéro de la semaine conformément aux règles définies dans la norme ISO 8601. Un exemple est fourni dans cet article.

Statut

Microsoft a confirmé l'existence de ce problème dans le fichier Oleaut32.dll.

Plus d'informations

La norme ISO 8601, largement utilisée en Europe, stipule, entre autres :

ISO 8601 Éléments de données et formats d'échange - Échange d'information - Représentation de la date et de l'heure
ISO 8601 : 1988 (E) paragraphe 3.17 :
« semaine, calendrier : période de sept jours d'une année de calendrier, commençant
un lundi et identifiée par son numéro ordinal dans l'année ;
la première semaine de calendrier d'année est celle qui inclut le
premier jeudi de cette année. Dans le calendrier grégorien, ceci équivaut
à la semaine qui inclut le 4 janvier. »
Cette norme peut être mise en oeuvre en appliquant les règles suivantes aux semaines de calendrier :
  • Une année est divisée en 52 ou 53 semaines.
  • Une semaine se compose de 7 jours, lundi étant le jour 1 et dimanche le jour 7.
  • La première semaine d'une année est celle qui comporte au moins 4 jours.
  • Si une année ne se termine pas un dimanche, soit ses derniers jours 1 à 3 appartiennent à la semaine 1 de l'année suivante, soit les premiers jours 1 à 3 de l'année suivante appartiennent à la dernière semaine de l'année en cours.
  • Seule une année qui commence ou qui se termine un jeudi possède 53 semaines.
Dans Visual Basic et Visual Basic pour Applications, toutes les fonctions de date, à l'exception de la fonction DateSerial, s'appuient sur des appels au fichier Oleaut32.dll. Étant donné que les fonctions Format() et DatePart() peuvent renvoyer le numéro de la semaine d'une date donnée, elles sont toutes deux concernées par ce bogue. Pour éviter ce problème, vous devez employer le code alternatif fourni dans cet article.

Procédures pour reproduire le problème

  1. Démarrez un projet EXE standard dans Visual Basic. Form1 est créé par défaut.
  2. Ajoutez deux CommandButtons à Form1.
  3. Copiez le code suivant dans la fenêtre de code de Form1 :
    Option Explicit

    Private Sub Command1_Click()
    ' 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 Sub

    Private Sub Command2_Click()
    ' 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 Sub
  4. Maintenez enfoncée la touche CTRL et appuyez sur la touche G pour ouvrir la fenêtre Exécution.
  5. Exécutez le projet, cliquez sur Command1 et notez les résultats suivants dans la fenêtre Exécution :

    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
    Vous remarquerez qu'avec ce format, comme toutes les semaines commencent un lundi, la date du 12/29/2003 sera considérée comme le début de la semaine 1, et non comme faisant partie de la semaine 53.
  6. Cliquez sur Command2 pour afficher la liste des dates dans la période spécifiée qui présentent ce problème. La liste inclut la date, le jour de la semaine (toujours Lundi), le numéro de la semaine renvoyé par Format (53) et le numéro de la semaine qui devrait être renvoyé (1.) Exemple :

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

Contournement

Si vous utilisez la fonction Format ou DatePart, vous devez vérifier la valeur renvoyée et, si c'est la valeur 53, revérifier et forcer le retour de 1, le cas échéant. Cet exemple de code explique la procédure :
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
Vous pouvez remplacer ces fonctions déterminant le numéro d'une semaine par un code mettant en oeuvre les règles de la norme ISO 8601 décrites plus haut. Dans l'exemple qui suit, une autre fonction est utilisée pour renvoyer le numéro de la semaine.

Exemple étape par étape

  1. Démarrez un projet EXE standard dans Visual Basic. Form1 est créé par défaut.
  2. Dans le menu Projet, ajoutez un nouveau module et collez-y le code suivant :
    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
  3. Ajoutez un CommandButton à Form1.
  4. Copiez le code suivant dans la fenêtre de code de Form1 :
    Private Sub Command1_Click()
    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 Sub
  5. Maintenez enfoncée la touche CTRL et appuyez sur la touche G pour ouvrir la fenêtre Exécution.
  6. Exécutez le projet et cliquez sur Command1 pour afficher les résultats suivants dans la fenêtre Exécution :

    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
    Vous remarquerez que Lundi (Monday) est considéré comme faisant partie de la semaine 1, comme il se doit.
Propriétés

ID d'article : 200299 - Dernière mise à jour : 19 sept. 2005 - Révision : 1

Commentaires