Feiertage berechnen und berücksichtigen - Teil 1

15.07.2006 von Helma  Spona
Wenn Sie wissen möchten, ob ein bestimmtes Datum ein Feiertag ist, beispielsweise um rechtlich relevante Fristen, die Laufzeit einer Sendung oder die Anzahl Werktage innerhalb eines Zeitraums zu berechnen, so ist das bei festen Feiertagen kein Problem. Anders sieht es bei variablen Feiertagen wie Rosenmontag, Ostern und Pfingsten aus. Wie sich diese berechnen lassen, erfahren Sie hier.

Die meisten variablen Feiertage lassen sich aus dem Termin von Ostern und Weihnachten berechnen. Für die Berechnung von Ostern gibt es eine Formel des Mathematikers Carl Friedrich Gauß, mit dem sich die Termine von Ostern bis in das Jahr 2499 berechnen lassen. Das reicht für die meisten Anwendungen aus.

Nachfolgend soll eine Klasse vorgestellt und erstellt werden, die Ihnen alle gesetzlichen und kirchlichen Feiertage berechnet und eine Methode zur Verfügung stellt, mit der Sie für ein bestimmtes Datum prüfen können, ob es sich dabei um einen Feiertag handelt und, wenn ja, um welchen.

Die Gaußsche Formel im Detail

Die Basis dieser Formel lautet Ostersonntag =D+E+22 und berechnet den Termin im Jahr, auf den Ostersonntag entfällt. Dabei berechnen sich D und E wie folgt:

D=((19*A)+M MOD 30)
E=((2*B)+(4*C)+(6 *D)+N) MOD 7

Die Variablen A, B und C berechnen sich wiederummwie folgt:

A= Jahr MOD 19
B= Jahr MOD 4
C= Jahr MOD 7

M und N stellen konstante Werte da, die abhängig vom Jahr feststehen. Sie berechnen sich wie Tabelle 1 zeigt.

Tabelle 1: Benötigte Werte für M und N.

Jahr

M

N

1582 – 1699

22

2

1700 – 1799

23

3

1800 – 1899

23

4

1900 – 2099

24

5

2100 – 2199

24

6

2200 – 2299

25

0

2300 – 2399

26

1

2400 – 2499

25

1

Haben Sie den Ostersonntag nach dieser Formel berechnet, müssen Sie noch ein paar weitergehende Ausnahmeregeln berücksichtigen.

Die Formel lässt sich daher verhältnismäßig einfach in eine Methode eines Klassenmoduls umsetzen.

Die Klasse erstellen

Um eine solche Klasse zu erstellen, fügen Sie in Ihre Access-Datenbank innerhalb der Entwicklungsumgebung mit Einfügen/Klassenmodul ein Klassenmodul ein und setzen dessen Name Eigenschaft auf clsFeiertage. Innerhalb der Klasse erstellen Sie eine öffentliche Funktion Ostersonntag() (Listing 1), der Sie als Parameter die Jahreszahl übergeben. Damit ist schon die erste Variable der Formel bekannt.

Public Function Ostersonntag(lngJahr As Long) As Variant
Dim strFehler As String
Dim lngTag As Long
Dim bytMonat As Byte
Dim bytA As Byte
Dim bytB As Byte
Dim bytM As Byte
Dim bytN As Byte
Dim lngE As Long
Dim lngD As Long
'Berechnen von A, B und C
bytA = lngJahr Mod 19
bytB = lngJahr Mod 4
bytC = lngJahr Mod 7
'Ermitteln von M und N
bytM = 0
bytN = 0
strFehler = getMundN(bytM, bytN, lngJahr)
If strFehler = "" Then
'Berechnung fortsetzen
'Berechnen von D
lngD = ((19 * bytA) + bytM) Mod 30
lngE = ((2 * bytB) + (4 * bytC) + (6 * lngD) + bytN) Mod 7
'Tag berechnen
lngTag = 22 + lngD + lngE
'Ausnahmen prüfen
'Ist Ostersonntag größer als 31, fällt Ostern in den April.
'Der Tag wird dann wie folgt berechnet: Ostersonntag =D+E-9.
If lngTag > 31 Then
bytMonat = 4
lngTag = lngD + lngE - 9
'weitere Ausnahmen prüfen
If lngTag = 26 Then
'Ist Ostersonntag der 26. April fällt Ostern
'auf den 19. April
lngTag = 19
ElseIf lngTag = 25 Then
'Ist Ostersonntag der 25. April und gleichzeitig
'A > 10 und D = 28, dann ist Ostersonntag der 18. April
If (bytA > 10) And (bytB = 28) Then
lngTag = 18
End If
End If
Else
bytMonat = 3
End If
End If
'Gültigkeitsprüfung durchführen
'Prüfen, ob das Datum gültig ist.
If (bytMonat > 0) And (lngTag > 0) Then
Ostersonntag = DateSerial(lngJahr, bytMonat, lngTag)
'prüfen, ob das berechnete Datum ein Sonntag ist.
If Weekday(Ostersonntag, vbSunday) > 1 Then
strFehler = "Berechnungsfehler: Ostersonntag " & _
"liegt nicht an einem Sonntag!"
Ostersonntag = strFehler & vbCrLf & "Berechnungergebnis: " _
& Format(Ostersonntag, "dd.MM.yyyy", vbSunday)
End If
Else
Ostersonntag = strFehler
End If
End Function
Private Function getMundN(ByRef bytM As Byte, ByRef bytN As Byte, _
lngJahr As Long) As String
getMundN = ""
Select Case lngJahr
Case 1582 To 1699:
bytM = 22
bytN = 2
Case 1700 To 1799:
bytM = 23
bytN = 3
Case 1800 To 1899:
bytM = 23
bytN = 4
Case 1900 To 2099:
bytM = 24
bytN = 5
Case 2100 To 2199:
bytM = 24
bytN = 6
Case 2200 To 2299:
bytM = 25
bytN = 0
Case 2300 To 2399:
bytM = 26
bytN = 1
Case 2400 To 2499:
bytM = 25
bytN = 1
Case Else
getMundN = "Die Jahreszahl muss zwischen 1581 und 2500 liegen!"
End Select
End Function

Am Anfang der Methode definieren Sie zunächst die benötigten Variablen und berechnen die Variablen A, B und C aus der Formel. Anschließend werden die Variablen bytM und bytN auf 0 gesetzt. Diese Variablen übergeben Sie dann die Funktion getMundN, die die Werte für M und N gemäß Tabelle 1 berechnet und zurückgibt. Damit zwei Werte von einer Funktion zurückgegeben werden können, werden diese als Referenz übergeben und so über die Parameter der Funktion zurückgegeben. Der Rückgabewert derFunktion ist eine leere Zeichenfolge, falls alles in Ordnung ist, oder eine Fehlermeldung.

Haben Sie durch Aufruf der Funktion die Werte für die Variablen bytM und bytN berechnet, prüfen Sie den Rückgabewert der Funktion. Ist dieser eine leere Zeichenkette, führen Sie die Berechnungdurch.

Gültigkeitsprüfung

Nach erfolgter Berechnung schließt sich noch eine Gültigkeitsprüfung an. Sie prüft, ob ein gültiges Datum berechnet wurde und ob es sich dabei um einen Sonntag handelt. Schließlich kannOstersonntag nicht auf einen anderen Wochentag entfallen. Wird kein Fehler erkannt, wird das Datum, andernfalls eine Fehlermeldung zurückgegeben.

Zur Berechnung der anderen Osterfeiertage wie Ostermontag und Karfreitag benötigen Sie nun nur noch kleine Methoden, die die Methode Ostersonntag aufrufen und einen Tag dazu addieren beziehungsweise für Karfreitag 2 Tage abziehen (Listing 2).

Public Function Ostermontag(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Ostersonntag(lngJahr)
If IsDate(varTemp) Then
Ostermontag = DateAdd("d", 1, varTemp)
Else
Ostermontag = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Karfreitag(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Ostersonntag(lngJahr)
If IsDate(varTemp) Then
Karfreitag = DateAdd("d", -2, varTemp)
Else
Karfreitag = varTemp 'Fehlermeldung zurückgeben
End If
End Function

Analog dazu berechnen sich auch die anderen von Ostern abhängigen Feiertage wie Karneval, Himmelfahrt und Pfingsten (Listing 3).

Public Function Pfingstsonntag(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Ostersonntag(lngJahr)
If IsDate(varTemp) Then
Pfingstsonntag = DateAdd("d", 49, varTemp)
Else
Pfingstsonntag = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Pfingstmontag(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Ostersonntag(lngJahr)
If IsDate(varTemp) Then
Pfingstmontag = DateAdd("d", 50, varTemp)
Else
Pfingstmontag = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Rosenmontag(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Ostersonntag(lngJahr)
If IsDate(varTemp) Then
Rosenmontag = DateAdd("d", -48, varTemp)
Else
Rosenmontag = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Fastnacht(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Ostersonntag(lngJahr)
If IsDate(varTemp) Then
Fastnacht = DateAdd("d", -47, varTemp)
Else
Fastnacht = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Aschermittwoch(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Ostersonntag(lngJahr)
If IsDate(varTemp) Then
Aschermittwoch = DateAdd("d", -46, varTemp)
Else
Aschermittwoch = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Himmelfahrt(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Ostersonntag(lngJahr)
If IsDate(varTemp) Then
Himmelfahrt = DateAdd("d", 39, varTemp)
Else
Himmelfahrt = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Fronleichnam(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Ostersonntag(lngJahr)
If IsDate(varTemp) Then
Fronleichnam = DateAdd("d", 60, varTemp)
Else
Fronleichnam = varTemp 'Fehlermeldung zurückgeben
End If
End Function

Von Weihnachten abhängige Feiertage

Neben Feiertagen, die von Ostern abhängig sind, gibt es auch einige, die Weihnachten als Bezugspunkt haben. Nun ist Weihnachten nicht das Problem, weil der erste Weihnachtstag in jedem Jahr auf den 25.12. fällt und Heiligabend immer am 24.12. ist. Allerdings müssen Sie zur Berechnung des ersten Advents berücksichtigen, dass der 1.Advent immer ein Sonntag ist, der 24.12. jedoch nicht. Fällt der 24.12. auf einen Sonntag, sind 4. Advent und Heiligabend identisch. Für die Berechnung sollten Sie also zunächst prüfen, ob der 24.12. des Jahres ein Sonntag ist. In diesem Fall ergibt sich der erste Advent aus dem 24.12. abzüglich 21 Tage (Listing 4). Andernfalls müssen Sie zunächst den vorherigen Sonntag ermitteln, das ist der 4. Advent, und davon ebenfalls 3 Wochen abziehen, um den ersten Advent zu berechnen.Ausgehend vom ersten Advent, können Sie die anderen Adventssonntage ermitteln. Weitere Feiertage berechnen sich wie folgt:

Public Function Advent1(lngJahr As Long) As Date
Dim datTemp As Date
Dim bytWochentag As Byte
'Prüfen, ob der 24. ein Sonntag ist
datTemp = DateSerial(lngJahr, 12, 24)
bytWochentag = Weekday(datTemp, 1)
If bytWochentag = 1 Then
Advent1 = DateAdd("d", -3 * 7, datTemp)
Else
Advent1 = DateAdd("d", -(3 * 7) - bytWochentag + 1, datTemp)
End If
End Function
Public Function Advent3(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Advent1(lngJahr)
If IsDate(varTemp) Then
Advent3 = DateAdd("d", 14, varTemp)
Else
Advent3 = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Advent2(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Advent1(lngJahr)
If IsDate(varTemp) Then
Advent2 = DateAdd("d", 7, varTemp)
Else
Advent2 = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Advent4(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Advent1(lngJahr)
If IsDate(varTemp) Then
Advent4 = DateAdd("d", 21, varTemp)
Else
Advent4 = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Bussundbettag(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Advent1(lngJahr)
If IsDate(varTemp) Then
Bussundbettag = DateAdd("d", -11, varTemp)
Else
Bussundbettag = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Volkstrauertag(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Advent1(lngJahr)
If IsDate(varTemp) Then
Volkstrauertag = DateAdd("d", -14, varTemp)
Else
Volkstrauertag = varTemp 'Fehlermeldung zurückgeben
End If
End Function
Public Function Totensonntag(lngJahr As Long) As Variant
Dim varTemp As Variant
varTemp = Advent1(lngJahr)
If IsDate(varTemp) Then
Totensonntag = DateAdd("d", -7, varTemp)
Else
Totensonntag = varTemp 'Fehlermeldung zurückgeben
End If
End Function

Weitere variable Feiertage

Außerdem gibt es noch zwei weitere variable Feiertage, die zwar nicht ganz so wichtig sind, aber hier der Vollständigkeit halber aufgeführt werden sollen: Muttertag und Erntedankfest (Listing 5).

Um diese Tage zu berechnen, sollten Sie sich eine Funktion programmieren, die den x-ten Sonntag eines bestimmten Monats berechnet. Diese Funktion können Sie dann verwenden, um beide Feiertage zu bestimmen.

Public Function SonntagImMonat(lngJahr As Long, _
bytMonat As Byte, bytSonntag As Byte) As Variant
Dim datTemp As Date
'Den ersten Sonntag suchen
datTemp = DateSerial(lngJahr, bytMonat, 1)
Do While Weekday(datTemp, vbSunday) > 1
datTemp = DateAdd("d", 1, datTemp)
Loop
'1. Sonntag gefunden
Select Case bytSonntag
Case 1:
SonntagImMonat = datTemp
Case 2:
SonntagImMonat = DateAdd("d", 7, datTemp)
Case 3:
SonntagImMonat = DateAdd("d", 14, datTemp)
Case 4:
SonntagImMonat = DateAdd("d", 21, datTemp)
Case 5:
SonntagImMonat = DateAdd("d", 28, datTemp)
Case Else
SonntagImMonat = "Der angegebene Sonntag liegt nicht in diesem Monat!"
End Select
'Prüfen, ob das berechnete Datum noch in dem
'gewünschten Monat liegt.
If Month(SonntagImMonat) > bytMonat Then
SonntagImMonat = "Der angegebene Sonntag liegt nicht in diesem Monat!"
End If
End Function
Public Function Erntedank(lngJahr As Long) As Date
Erntedank = SonntagImMonat(lngJahr, 10, 1)
End Function
Public Function Muttertag(lngJahr As Long) As Date
Muttertag = SonntagImMonat(lngJahr, 5, 2)
If Muttertag = Pfingstsonntag(lngJahr) Then
Muttertag = DateAdd("d", -7, lngJahr)
End If
End Function

Prüfen, ob ein Datum ein Feiertag ist

Wenn es Sie weniger interessiert, wann welche Feiertage liegen, als vielmehr, ob ein bestimmtes Datum irgendein Feiertag ist, sollten Sie dazu noch eine Methode erstellen, die ein vorhandenes Datum daraufhin prüft, ob es sich dabei um einen der berechneten Feiertage handelt (Listing 6).

Public Function istFeiertag(ByRef datDatum As Date, ByRef strName As String) _
As Boolean
Dim lngJahr As Long
lngJahr = Year(datDatum)
If Not IsDate(datDatum) Then
strName = "Das Datum ist nicht gültig!"
istFeiertag = False
Exit Function
Else
If Ostersonntag(lngJahr) = datDatum Then
strName = "Ostersonntag"
istFeiertag = True
ElseIf Karfreitag(lngJahr) = datDatum Then
strName = "Karfreitag"
istFeiertag = True
ElseIf Ostermontag(lngJahr) = datDatum Then
strName = "Ostermontag"
istFeiertag = True
ElseIf Pfingstsonntag(lngJahr) = datDatum Then
strName = "Pfingstsonntag"
istFeiertag = True
ElseIf Pfingstmontag(lngJahr) = datDatum Then
strName = "Pfingstmontag"
istFeiertag = True
ElseIf Advent1(lngJahr) = datDatum Then
strName = "1. Advent"
istFeiertag = True
ElseIf Advent2(lngJahr) = datDatum Then
strName = "2. Advent"
istFeiertag = True
ElseIf Advent3(lngJahr) = datDatum Then
strName = "3. Advent"
istFeiertag = True
ElseIf Advent4(lngJahr) = datDatum Then
strName = "4. Advent"
istFeiertag = True
ElseIf Bussundbetag(lngJahr) = datDatum Then
strName = "Buß- und Betag"
istFeiertag = True
ElseIf Totensonntag(lngJahr) = datDatum Then
strName = "Totensonntag"
istFeiertag = True
ElseIf Volkstrauertag(lngJahr) = datDatum Then
strName = "Volkstrauertag"
istFeiertag = True
ElseIf Erntedank(lngJahr) = datDatum Then
strName = "Erntedank"
istFeiertag = True
ElseIf Muttertag(lngJahr) = datDatum Then
strName = "Muttertag"
istFeiertag = True
Else
'Prüfen, ob ein fixer Feiertag vorliegt
strName = FixFeiertag(datDatum)
If strName > "" Then
istFeiertag = True
End If
End If
End If
End Function
Public Function FixFeiertag(datDatum As Date) As String
'Prüft, ob es sich bei dem Datum um einen fixen Feiertag handelt
'Falls ja, wird der Name des Feiertags zurückgegeben
FixFeiertag = ""
End Function
Public Function IstSonntag(datDatum As Date) As Boolean
If Weekday(datDatum, vbSunday) = 1 Then
IstSonntag = True
Else
IstSonntag = False
End If
End Function
Public Function IstSamstag(datDatum As Date) As Boolean
If Weekday(datDatum, vbSunday) = 7 Then
IstSamstag = True
Else
IstSamstag = False
End If
End Function

Dazu übergeben Sie das zu prüfende Datum an die Methode, die alle eben erstellten Methoden aufruft und prüft, ob das Datum mit dem Rückgabewert der Methode übereinstimmt. Wird eine Übereinstimmung gefunden, wird der Rückgabewert auf true gesetzt. Über den zweiten Parameter der Funktion können Sie dann den Namen des Feiertags zurückgeben. Damit auch fixe Feiertage berücksichtigt werden, wird im Else-Zweig die Methode FixFeiertag aufgerufen. Sie gibt den Namen des Feiertags zurück, der dem übergebenen Datum entspricht. Ist das Datum kein Feiertag, wird eine leere Zeichenfolge zurückgegeben. Vorerst ist die Funktion jedoch noch leer. Sie wird im nächsten Teil dieser Artikelfolge gefüllt.

Optimal wäre außerdem noch eine Funktion, die True zurückgibt, wenn das übergebene Datumein Sonntag oder ein Samstag ist. Damit Sie das unterscheiden können, sollten Sie dazu zwei verschiedene Methoden erstellen.

Wie geht es weiter?

Damit haben Sie nun alle Methoden erstellt, die Sie zur Berechnung der variablen Feiertage benötigen. Im nächsten Teil der Artikelfolge wird eine Methode gezeigt, mit der Sie feste Feiertage erfassen und berücksichtigen können. Darüber hinaus erfahren Sie, wie Sie die Klasse sinnvoll einsetzen.