Calculer la date de Pâques

Je vous propose une fonction VBA qui détermine la date du Dimanche de Pâques pour toute année (de 325 à 9999) dont découlent les jours fériés mobiles en France.
Elle complète également la fonction fournie dans la FAQ Access qui n’est valide qu’entre 1982 et 2048.

La fonction VBA
L’objectif est la performance et non l’aspect didactique des calculs…

Public Function Paques(ByVal an As Integer) As Date
'Calcul de la date du dimanche de Pâques à partir de l'année 325
'Performance par million d'appel :
'   - Entre 325 et 1582 et entre 1900 et 2099   => 1/4 de seconde
'   - Année supérieure à 1582 hors 1900 - 2099 => 1/2 de seconde
'Philben - v1.0 - Free to use
  Dim a As Integer, b As Integer, c As Integer, d As Integer, e As Integer, f As Integer
   If an < 10000 Then    'Limite supérieure des dates sous Access (31 décembre 9999)
     Select Case an
      Case 1900 To 2099    'Algorithme de Carter
        a = (204 - 11 * (an Mod 19)) Mod 30 + 22
         Paques = DateSerial(an, 3, a + 6 + (a > 49) - (an + an \ 4 + a + (a > 49)) Mod 7)
      Case Is > 1582    'Proposé en 1876 dans la revue Nature (dérivé de l'algorithme de Delambre)
        a = an Mod 19: b = an \ 100: c = an Mod 100
         d = (19 * a + b - b \ 4 - (b - (b + 8) \ 25 + 1) \ 3 + 15) Mod 30
         e = (32 + 2 * (b Mod 4) + 2 * (c \ 4) - d - c Mod 4) Mod 7
         f = d + e - 7 * ((a + 11 * d + 22 * e) \ 451) + 114
         Paques = DateSerial(an, f \ 31, f Mod 31 + 1)
      Case Is > 324    'Algorithme de Oudin pour les dates juliennes < 1583 décrit par Claus Tondering
        a = (19 * (an Mod 19) + 15) Mod 30
         Paques = DateSerial(an, 3, 28 + a - (an + an \ 4 + a) Mod 7)
      End Select
   End If
End Function

 
Pourquoi trois périodes de temps ?
La date de Pâques (Easter en anglais) a été fixée au concile de Nicée en 325 ce qui explique l’année de départ de la fonction. De l’an 325 à 1582, on utilise l’algorithme de Oudin adapté au calendrier julien puis l’algorithme de la revue Nature pour le calendrier grégorien actuel (réforme du 15 octobre 1582). Enfin, l’algorithme de Carter est utilisé pour la période actuelle (1900 à 2099) compte tenu de sa rapidité de calcul.

Performance
Pour un million d’appels de la fonction :

  • Compter 1/4 de seconde pour la période entre 325 et 1582 et entre 1900 et 2099;
  • Compter 1/2 seconde pour les années supérieures à 1582 (hors 1900 à 2099).

Vérification des résultats
La méthode retenue est de comparer les résultats avec d’autres algorithmes.
Concernant le calendrier julien j’ai utilisé l’algorithme de Delambre versus Oudin :

Public Function TestPaquesJulien()
   Dim a As Integer, b As Integer, c As Integer, d As Integer, e As Integer, an As Integer, bErr As Boolean
   Dim d1 As Date, d2 As Date

   Debug.Print "Test calendrier julien..."
   For an = 325 To 1582

      'Delambre
     a = an Mod 19
      b = an Mod 7
      c = an Mod 4
      d = (19 * a + 15) Mod 30
      e = (2 * c + 4 * b - d + 34) Mod 7
      d1 = DateSerial(an, 3, 22 + d + e)

      d2 = Paques(an)
      If d1 <> d2 Then
         Debug.Print vbTab & "-> Date différente pour l'année " & an & " entre Delambre (" & d1 & ") et la fonction Paques (" & d2 & ")"
         bErr = True
         Exit For
      End If
   Next an
   Debug.Print "Fin - " & IIf(bErr, "Ecart constaté !", "Pas d'écart constaté")

End Function

 
Pour le calendrier grégorien, j’ai utilisé l’algorithme de Oudin versus nos deux algorithmes (Carter et Nature) :

Public Function TestPaquesGregorien()
   Dim c As Integer, g As Integer, k As Integer, i As Integer, j As Integer, l As Integer, m As Integer
   Dim an As Integer, bErr As Boolean, d1 As Date, d2 As Date

   Debug.Print "Test calendrier grégorien..."
   For an = 1583 To 9999

      'Oudin - http://www.smart.net/~mmontes/oudin.html
     g = an Mod 19
      c = an \ 100
      k = (c - 17) \ 25
      i = (c - c \ 4 - (c - k) \ 3 + 19 * g + 15) Mod 30
      i = i - (i \ 28) * (1 - (i \ 28) * (29 \ (i + 1)) * ((21 - g) \ 11))
      j = (an + an \ 4 + i + 2 - c + c \ 4) Mod 7
      l = i - j
      m = 3 + (l + 40) \ 44
      d1 = DateSerial(an, m, l + 28 - 31 * (m \ 4))

      d2 = Paques(an)
      If d1 <> d2 Then
         Debug.Print vbTab & "-> Date différente pour l'année " & an & " entre Oudin (" & d1 & ") et la fonction Paques (" & d2 & ")"
         bErr = True
         Exit For
      End If
   Next an
   Debug.Print "Fin - " & IIf(bErr, "Ecart constaté !", "Pas d'écart constaté")

End Function

 
On peut également vérifier que la date de Pâques calculée soit comprise entre le 22 mars et le 25 avril et qu’elle soit bien un dimanche. Comme la fonction VBA Weekday() n’est adaptée qu’au calendrier grégorien, il faut donc ajouter un algorithme qui détermine les jours de semaine du comput julien (années < 1583 dans notre cas).
La fonction de test et JourSemaineJulien() :

Public Function TestPaques()
'Quelque soit l'année, vérifier que la date de Pâques est bien :
'   - comprise entre le 22 mars et 25 avril
'   - un dimanche
  Const cMinAn As Integer = 325
   Const cMaxAn As Integer = 9999
   Const cMaxMoisJour As Integer = 425
   Const cMinMoisJour As Integer = 322
   Const cMinCalendrierGregorien As Integer = 1583
   Dim p As Date, i As Integer, md As Integer, j As Integer

   For i = cMinAn To cMaxAn
      p = Paques(i)
      md = Month(p) * 100 + Day(p)
      If i < cMinCalendrierGregorien Then j = JourSemaineJulien(p) Else j = Weekday(p)
      If j <> vbSunday Then
         Debug.Print "Erreur ! Une date de Pâques n'est pas un dimanche : ", p
         Exit For
      End If
      If md < cMinMoisJour Then
         Debug.Print "Erreur ! Une date de Pâques est inférieure au 22 mars : ", p
         Exit For
      ElseIf md > cMaxMoisJour Then
         Debug.Print "Erreur ! Une date de Pâques est supérieure au 25 avril : ", p
         Exit For
      End If
   Next i
   If i > cMaxAn Then Debug.Print "Aucune erreur détectée entre " & cMinAn & " et " & cMaxAn
End Function

Public Function JourSemaineJulien(ByVal LaDate As Date) As VbDayOfWeek
'Formule de Zeller - http://www.vendredi13.us/ZellerMethode.html
  Dim a As Integer, m As Integer, s As Integer, y As Integer, z As Integer

   y = Year(LaDate): m = Month(LaDate)
   If m < 3 Then m = 12 + m: y = y - 1
   s = y \ 100: a = y - s * 100

   'Comput julien : de 100 (1ère date Access : 1/1/100) à 1582
  z = (Day(LaDate) + Int(2.6 * (m + 1)) + a + a \ 4 + 5 - s) Mod 7

   'comput grégorien : > 1582
  'z = (Day(LaDate) + Int(2.6 * (m + 1)) + a + a \ 4 + s \ 4 - 2 * s) Mod 7
  'If z < 0 Then z = 7 + (z Mod 7) 'inutile entre 100 et 1582

   If z = 0 Then z = vbSaturday   'alignement sur VbDayOfWeek

   JourSemaineJulien = z
End Function

 
J'ai également comparé les résultats d'un échantillon d'années avec ceux de ce site web très intéressant sans constater d’erreur.

Exemples
A copier dans la fenêtre ‘Exécution’ de VBE puis mettre le focus sur la ligne à exécuter et taper sur ‘Entrée’.

'Dimanche de Pâques pour l'année en cours
?Paques(Year(Date())) 'pour 2012 -> 08/04/2012
'An 1000 - Calendrier julien
?Paques(1000) '31/03/1000
?Paques(3000) '13/04/3000

 
Remarque
Bien que l’année grégorienne (365.2425 jours en moyenne) soit plus courte que l’année julienne (365.25 jours en moyenne) , elle est encore trop longue par rapport à l’année tropique (365.2422 jours en moyenne). De plus, le raccourcissement de l’année tropique et l’allongement du jour au fil des millénaires nécessiteront des ajustements qui rendront caduques les algorithmes utilisés…

Références
Cette présentation en français au format PDF de P. Rocher (Observatoire de Paris) fait un point détaillé sur les dates de Pâques, les méthodes de calcul et présente l’algorithme de la revue Nature (page 15).
Le site vendredi13.us dont le design pique un peu les yeux (!) est d’une richesse exceptionnelle sur le vendredi 13 et les dates de Pâques, le tout en français ! Il expose très clairement de nombreuses méthodes de calcul du jour de Pâques dont Gauss, Delambre, Oudin, Reints, Zeller…

@+

Philippe

Laisser un commentaire