Feiertage per VBA ermitteln

Wenn Sie mit Access Termine verwalten, werden Sie auch Feiertage berücksichtigen wollen. Die entsprechenden Datumsangaben liefert Access leider nicht frei Haus – Sie müssen selbst eine entsprechende Funktion bereitstellen. Der vorliegende Beitrag zeigt, wie Sie die Feiertage ermitteln und wie Sie schnell prüfen, ob ein Feiertag auf ein bestimmtes Datum fällt.

Es gibt verschiedene Gruppen von Feiertagen: Solche, die jedes Jahr auf den gleichen Termin fallen (wie Neujahr, Weihnachten oder der Tag der deutschen Einheit), Tage, die vom Datum des Ostersonntags abhängen, das auf komplizierte Art berechnet wird, und Tage, die vom Datum des vierten Advents abhängen. Außerdem müssen Sie bei der Ermittlung von Feiertagen berücksichtigen, dass es einige Feiertage nur in bestimmten Bundesländern gibt.

So ist Heilige Dreikönige am 6. Januar nur in acht Bundesländern ein Feiertag, in den übrigen Bundesländern wird gearbeitet. Außerdem ändern sich die Feiertage der verschiedenen Bundesländer gelegentlich oder es kommen neue Feiertage hinzu – so ist der Internationale Frauentag als Feiertag noch recht neu und wird nur in Berlin begangen und der Weltkindertag findet als Feiertag nur in Thüringen statt.

Tabellen oder reiner Code?

Vor dem Zusammenstellen der Lösung zu diesem Beitrag stand die Frage im Raum, ob man die Basisdaten zu den Feiertagen in Tabellen speichert oder ob man diese komplett per VBA-Code ermittelt. In Anbetracht dessen, dass die Feiertage nur alle Jubeljahre geändert werden, haben wir uns für die reine VBA-Variante entschieden – auch vor dem Hintergrund, dass Sie so nur ein einziges Standardmodul in Ihre Anwendung kopieren müssen, wenn Sie die dynamische Ermittlung der Feiertage verwenden möchten. Der Einsatz einer reinen VBA-Lösung bedeutet zunächst, dass einige grundlegende Informationen in Enumerationen gespeichert werden. Die erste Enumeration heißt eBundesland und nimmt alle Bundesländer in Form entsprechender Konstanten und Zahlenwerte auf. Sie sieht wie folgt aus:

Public Enum eBundesland
     eBadenWuerttemberg = 1
     eBayern = 2
     eBerlin = 4
     eBrandenburg = 8
     eBremen = 16
     eHamburg = 32
     eHessen = 64
     eMecklenburgVorpommern = 128
     eNiedersachsen = 256
     eNordrheinWestfalen = 512
     eRheinlandPfalz = 1024
     eSaarland = 2048
     eSachsen = 4096
     eSachsenAnhalt = 8192
     eSchleswigHolstein = 16384
     eThueringen = 32768
End Enum

Warum nun erhalten die Konstanten für die Bundesländer ausschließlich Zweierpotenzen als Zahlenwerte? Nun: So können wir einfach festlegen und ermitteln, welcher Feiertag in welchem Bundesland gefeiert wird. Dazu nutzen wir eine zweite Enumeration, welche die Feiertage auflistet und deren Zahlenwert Informationen darüber liefert, welche Bundesländer den jeweiligen Feiertag begehen. Diese Enumeration können Sie in Kurz- oder Langform verwenden. Hier ist eine abgekürzte Version der Langform:

Public Enum eFeiertageBundeslaender
     eNeujahr = eBadenWuerttemberg + eBayern + eBerlin  + eBrandenburg + eBremen + eHamburg + eHessen +  eMecklenburgVorpommern + eNiedersachsen +  eNordrheinWestfalen + eRheinlandPfalz + eSaarland +  eSachsen + eSachsenAnhalt + eSchleswigHolstein +  eThueringen
     ...
     eWeltkindertag = eThueringen
     eInternationalerFrauentag = eBerlin
End Enum

Wie gut zu erkennen ist, werden zu jedem Feiertag die Konstanten aller Bundesländer summiert, in denen der jeweilige Feiertag stattfindet. Da wir den Konstanten der Bundesländer Zweierpotenzen zugewiesen haben, können wir theoretisch statt der etwas längeren Ausdrücke auch einen Zahlenwert angeben. Für Feiertage, die in allen Bundesländern stattfinden, wäre dies etwa 32.768 + 16.384 + 8.192 + 4.096 + 2.048 + 1.024 + 512 + 256 + 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1, also 65.535. Allerdings lassen sich die Informationen doch besser als Konstante prüfen.

Ermittlung des Datums des vierten Advents

Der vierte Advent ist der letzte Sonntag vor dem ersten Weihnachtstag und kann somit auch auf Heiligabend fallen. Der erste bis vierte Advent sind in allen Bundesländern Feiertage. Der erste, zweite und dritte Advent wird jeweils an den Sonntagen vor dem vierten Advent gefeiert, somit sind diese Feiertage vom Datum des vierten Advents abhängig. Auch der Buß- und Bettag, der allerdings nur in Sachsen Feiertag ist, hängt vom Datum des vierten Advents ab. Den vierten Advent berechnen wir so:

Function VierterAdvent(intJahr As Integer) As Date
     Dim dat As Date
     dat = CDate("24.12." & intJahr)
     Do While Not Weekday(dat, vbSunday) = vbSunday
         dat = dat - 1
     Loop
     VierterAdvent = dat
End Function

Diese Funktion stellt den Wert der Variablen dat einfach auf den 24. Dezember des Jahres ein, für welches das Datum des vierten Advents ermittelt werden soll, und wird in einer Do While-Schleife so lange um jeweils einen Tag in Richtung Jahresbeginn verschoben, bis es auf einen Sonntag fällt. Dies kann auch gleich beim ersten Durchlauf der Fall sein – dann fällt Heiligabend genau auf den vierten Advent. Die vom vierten Advent abhängigen Feiertage werden dann durch Subtraktion der entsprechenden Anzahl Tage ermittelt.

Ermittlung des Datums des Ostersonntags

Die Ermittlung des Ostersonntags erfolgt durch eine recht komplizierte Berechnung, die durch die folgende Funktion abgebildet wird:

Function Ostersonntag(intJahr As Integer) As Date
     Dim a As Integer
     Dim b As Integer
     Dim c As Integer
     Dim d As Integer
     Dim e As Integer
     Dim intTag As Integer
     Dim intMonat As Integer
     a = intJahr Mod 19
     b = intJahr Mod 4
     c = intJahr Mod 7
     d = (19 * a + 24) Mod 30
     e = (2 * b + 4 * c + 6 * d + 5) Mod 7
     intTag = 22 + d + e
     intMonat = 3
     If intTag > 31 Then
         intTag = d + e - 9
         intMonat = 4
     End If
     Ostersonntag = CDate(intTag & "."  & intMonat & "." & intJahr)
End Function

Diese Funktion sorgt dafür, dass der Ostersonntag in Abhängigkeit von der Jahreszahl an einem Tag zwischen dem 23. März und dem 26. April liegt. Wenn man sich überlegt, dass Ostern (und die davon abhängigen Tage) genau wie Weihnachten Jahrestage bestimmter Ereignisse sind, fragt man sich schon, warum hier keine einfachere Regelung gefunden wurde …

Array der Feiertage zusammenstellen

Die grundlegenden Funktionen und Enumerationen haben wir nun zusammengestellt. Es fehlt noch eine Funktion, die alle Informationen zusammenführt und ein Array zurückliefert, das alle Feiertagsdaten für ein per Parameter angegebenes Jahr enthält. Ein zweiter Parameter gibt an, für welches Bundesland die Feiertage zusammengestellt werden sollen. Dies alles erledigt die Funktion FeiertageArray aus Listing 1. Der erste Parameter erwartet die Angabe einer Jahreszahl, also beispielsweise 2022, der zweite eine der Konstanten der Enumeration der Bundesländer, also etwa eNordrheinWestfalen.

Public Function FeiertageArray(intJahr As Integer, lngBundesland As eBundesland) As Variant
     Dim arr() As String, i As Integer
     Dim datOstersonntag As Date, datVierterAdvent As Date
     datOstersonntag = Ostersonntag(intJahr)
     datVierterAdvent = VierterAdvent(intJahr)
     If (eNeujahr And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Neujahr", ISODatum("1.1." & intJahr)
     If (eHeiligeDreiKoenige And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, _
         arr, "Heilige drei Könige", ISODatum("6.1." & intJahr)
     If (eKarfreitag And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Karfreitag", _
         ISODatum(datOstersonntag - 2)
     If (eOstersonntag And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Ostersonntag", _
         ISODatum(datOstersonntag)
     If (eOstermontag And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Ostermontag", _
         ISODatum(datOstersonntag + 1)
     If (eTagDerArbeit And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Tag der Arbeit", _
         ISODatum("1.5." & intJahr)
     If (eChristiHimmelfahrt And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, _
         "Christi Himmelfahrt", ISODatum(datOstersonntag + 39)
     If (ePfingstmontag And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Pfingstmontag", _
         ISODatum(datOstersonntag + 50)
     If (eFronleichnam And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Fronleichnam", _
         ISODatum(datOstersonntag + 60)
     If (eMariaHimmelfahrt And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Maria Himmelfahrt", _
         ISODatum("15.8." & intJahr)
     If (eTagDerDeutschenEinheit And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, _
         "Tag der deutschen Einheit", ISODatum("3.10." & intJahr)
     If (eReformationstag And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Reformationstag", _
         ISODatum("31.10." & intJahr)
     If (eAllerheiligen And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Allerheiligen", _
         ISODatum("1.11." & intJahr)
     If (eBussUndBettag And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Buß- und Bettag", _
         ISODatum(datVierterAdvent - 32)
     If (eErsterAdvent And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Erster Advent", _
         ISODatum(datVierterAdvent - 21)
     If (eZweiterAdvent And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Zweiter Advent", _
         ISODatum(datVierterAdvent - 14)
     If (eDritterAdvent And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Dritter Advent", _
         ISODatum(datVierterAdvent - 7)
     If (eVierterAdvent And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Vierter Advent", _
         ISODatum(datVierterAdvent)
     If (eErsterWeihnachtstag And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, _
         "Erster Weihnachtstag", ISODatum("25.12." & intJahr)
     If (eZweiterWeihnachtstag And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, _
         "Zweiter Weihnachtstag", ISODatum("26.12." & intJahr)
     If (eWeltkindertag And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, "Weltkindertag", _
         ISODatum("20.9." & intJahr)
     If (eInternationalerFrauentag And lngBundesland) = lngBundesland Then FeiertagHinzufuegen i, arr, _
         "Internationaler Frauentag", ISODatum("8.3." & intJahr)
     FeiertageArray = arr
End Function

[

Listing 1: Ermitteln aller Feiertage eines Jahres für ein Bundesland

Die Funktion soll ein Array mit den Feiertagen zurückliefern, wobei der erste Wert den Namen des Feiertags enthält und der zweite das Datum des Feiertags. Die Daten der beiden Stichtage (Ostersonntag und der vierte Advent) werden in den Variablen datOs-tersonntag und datVierterAdvent gespeichert und später zur Berechnung der davon abhängigen Feiertage herangezogen. Die weiter oben vorgestellten Funktionen Ostersonntag und VierterAdvent füllen die beiden Datumsvariablen datOstersonntag und datVierter-Advent gleich zu Beginn der Funktion.

Dann prüft die Prozedur für jeden Feiertag, ob dieser in dem mit dem Parameter lngBundesland übergebenen Bundesland begangen wird. Dabei macht sich die Funktion die Enumerationen zunutze: Ein Ausdruck wie (eNeujahr And lngBundesland) = lngBundesland liefert so etwa den Wert True zurück, wenn der Feiertag im angegebenen Bundesland gefeiert wird. Wie funktioniert das? eNeujahr entspricht genau wie lngBundesland einem Zahlenwert. eNeuJahr hat den Zahlenwert 65.535, das Bundesland eNordrheinWestfalen entspricht beispielsweise dem Wert 1. Dadurch, dass wir den Bundesländern Zweierpotenzen als Wert zugewiesen haben, können wir durch einen Ausdruck wie eNeujahr And lngBundesland ermitteln, ob eNordrheinWestfalen in eNeuJahr enthalten ist. Diese arithmetische beziehungsweise binäre Und-Verknüpfung liefert genau den Wert der gemeinsamen Zweierpotenz, in diesem Fall 1 – dieses Bundesland begeht also diesen Feiertag. Wenn dies der Fall ist, ruft die Funktion eine weitere Funktion namens FeiertagHinzufuegen mit einigen Parametern auf:

FeiertagHinzufuegen i, arr, "Neujahr", ISODatum("1.1." & intJahr)

Der Parameter i enthält die laufende Nummer des aktuell abgearbeiteten Feiertags, arr ist das Array mit der zu füllenden Liste der Feiertage, der dritte Parameter enthält die Bezeichnung des Feiertags und der vierte das mit der Funktion ISODatum im Format #yyyy/mm/dd# formatierte Datum. Die Funktion FeiertagHinzufuegen sieht so aus:

Public Function FeiertagHinzufuegen(i, arr() As String,  strFeiertag As String, strDatum As String)
     ReDim Preserve arr(2, i)
     arr(0, i) = strFeiertag
     arr(1, i) = strDatum
     i = i + 1
End Function

Die Funktion fügt lediglich den Namen des Feiertags und das Datum zum Array hinzu und erhöht den Zähler i um 1. Dadurch, dass alle Parameter standardmäßig mit ByRef übergeben werden, spiegeln sich die Änderungen direkt in den Variablen der aufrufenden Funktion wider.

Verwenden des Arrays

Das resultierende Array können Sie auf verschiedene Arten weiternutzen. Wenn Sie beispielsweise alle Feiertage einfach im Direktfenster ausgeben möchten, verwenden Sie die folgende Prozedur:

Public Function FeiertageAusgeben()
     Dim arr As Variant
     Dim i As Integer
     arr = Feiertage(2022, eNordrheinWestfalen)
     For i = LBound(arr, 2) To UBound(arr, 2)
         Debug.Print arr(0, i), arr(1, i)
     Next i
End Function

Beachten Sie, dass Sie der LBound– und der UBound-Funktion zum Ermitteln der unteren und oberen Grenze des Arrays einen zweiten Parameter mitgeben müssen. Dieser gibt an, dass die Größe der zweiten Dimension ausgelesen werden soll.

Der Grund dafür, dass wir die Zählervariable in die zweite Dimension geschrieben haben, ist der, dass die Redim-Anweisung ausschließlich die Größe der letzten Dimension anpassen kann. Der Inhalt des von FeiertageArray zurückgegebenen Arrays sieht im Direktfenster wie in Bild 1 aus.

Ausgabe der Feiertage eines Bundeslandes im Direktbereich des VBA-Editors

Bild 1: Ausgabe der Feiertage eines Bundeslandes im Direktbereich des VBA-Editors

Feiertage in einer Collection speichern

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

6 Kommentare

  1. Hallo Herr Minhorst, leider funktioniert der Schreibvorgang in die Tabelle nicht. Der Code:
    Public Sub FeiertageInTabelle(intJahr As Integer, lngBundesland As eBundesland)
    Dim db As DAO.Database
    Dim arr As Variant
    Dim i As Integer
    Set db = CurrentDb
    arr = FeiertageArray(intJahr, lngBundesland)
    db.Execute “DELETE FROM tblFeiertage”, dbFailOnError
    For i = LBound(arr, 2) To UBound(arr, 2)
    db.Execute “INSERT INTO tblFeiertage(Feiertagsname, Feiertagsdatum) VALUES(”” & arr(0, i) & “”, ”” & arr(1, i) & “”)”, dbFailOnError
    Next i
    Set db = Nothing
    End Sub
    die Zeile db.Execute liefert eine Fehlermeldung 3075, Syntaxfehler in Abfrageausdruck “Neujahr”.
    Der gleiche Fehler kommt auch, wenn ich die Zeile db.Excute mit dem Unterstrich, wie oben im Text schreibe.
    Andreas Irmer

  2. Hallo,
    Datenbank funktioniert bei mir unter Microsoft 365 auch nicht.
    Gibt es auch die Möglichkeit eine Selektion der Feiertage für alle Bundesländer in eine Tabele zu schreiben und diese dann mit dem Datum einer Eingabe Tabelle zu vergleichen?

    Hannen

Schreibe einen Kommentar