Les fonctions Format ou DatePart peuvent renvoyer un numéro de semaine incorrect pour le dernier lundi de l’année

Avertissement

Il existe un problème avec l’utilisation de cette fonction. Le dernier lundi de certaines années civiles peut être retourné en tant que semaine 53 alors qu’il devrait être la semaine 1. Pour plus d’informations et une solution de contournement, consultez Les fonctions Format ou DatePart peuvent retourner un numéro de semaine incorrect 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 des dates à l’aide de la syntaxe suivante :

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

le dernier lundi de certaines années civiles est retourné comme semaine 53 alors qu’il 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 fonction sous-jacent au fichier Oleaut32.dll retourne 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 pour renvoyer le numéro de semaine en fonction des règles de la norme ISO 8601. Un exemple est inclus dans cet article.

Informations supplémentaires

La norme ISO 8601 est largement utilisée en Europe et inclut les éléments suivants :

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

Cela peut être implémenté en appliquant ces règles pour les semaines de calendrier :

  • Une année est divisée en 52 ou 53 semaines civiles.
  • Une semaine civile a 7 jours. Lundi est jour 1, dimanche est jour 7.
  • La première semaine civile d’une année est celle qui contient au moins 4 jours.
  • Si une année n’est pas conclue un dimanche, ses 1 à 3 derniers jours appartiennent à la première semaine civile de l’année suivante ou les 1 à 3 premiers jours de l’année suivante appartiennent à la dernière semaine civile de l’année actuelle.
  • Seule une année commençant ou se terminant le jeudi compte 53 semaines calendaires.

Dans Visual Basic et Visual Basic pour Applications, toutes les fonctionnalités de date, à l’exception de la fonction DateSerial, proviennent des appels au fichier Oleaut32.dll. Étant donné que les fonctions Format() et DatePart() peuvent renvoyer le numéro de semaine calendaire pour une date donnée, ces deux fonctions sont affectées par ce bogue. Pour éviter ce problème, vous devez utiliser l’autre code fourni par cet article.

Étapes pour reproduire le comportement

  1. Ouvrez le projet Visual Basic dans une application Office (Alt + F11).

  2. Dans le menu Projet , ajoutez un nouveau module.

  3. Collez le code suivant dans le module :

    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. Utilisez (Ctrl + G) pour ouvrir la fenêtre Exécution si elle n’est pas déjà ouverte.

  5. Tapez ? Test1 dans la fenêtre Exécution et appuyez sur Entrée, 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
    

    Avec ce format, toutes les semaines commencent par lundi, de sorte que le 29/12/2003 doit être considéré comme le début de la semaine 1 et non comme faisant partie de la semaine 53.

  6. Tapez ? Test2 dans la fenêtre Exécution et appuyez sur Entrée pour afficher la liste des dates dans la plage spécifiée qui rencontrent ce problème. La liste inclut la date, le jour de la semaine (toujours lundi), le numéro de semaine retourné par format (53) et le numéro de semaine qu’il doit retourner (1.) Par 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
    ...
    

Solutions de contournement

Si vous utilisez les fonctions Format ou DatePart, vous devez case activée la valeur de retour. Quand il est 53, exécutez une autre case activée et forcez un retour de 1, si nécessaire. Cet exemple de code illustre une façon de procéder :

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 éviter d’utiliser ces fonctions pour déterminer le numéro de semaine en écrivant du code qui implémente les règles ISO 8601 décrites ci-dessus. L’exemple suivant illustre une fonction de remplacement pour retourner le numéro de semaine.

Exemple pas à pas

  1. Ouvrez le projet Visual Basic dans une application Office (Alt + F11).

  2. Dans le menu Projet , ajoutez un nouveau module.

  3. Collez le code suivant dans le module :

    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. Utilisez (Ctrl + G) pour ouvrir la fenêtre Exécution si elle n’est pas déjà ouverte.

  5. Tapez ? Test3 dans la fenêtre Exécution et appuyez sur Entrée, notez 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
    

    Notez que le lundi est considéré comme étant la semaine 1 comme il devrait l’être.