Feiertagsberechnung mit Python

Veröffentlicht am 1. Januar 2022 16:07

Wenn in einem Programm Datumsangaben o.ä. verwendet werden, kommt man oft zu dem Problem, dass man gerne die Feiertage berücksichtigen möchte. Die festen Feiertage sind normalerweise kein Problem. Wie aber werden die beweglichen Feiertage ermittelt? Wann ist im übernächsten Jahr der Pfingstmontag? Muss man für jedes Jahr die Daten irgendwo hinterlegen?

Natürlich gibt es auch für die beweglichen Feiertage eine Lösung. Da diese Feiertage alle christliche Feiertage sind, hängen diese vom Ostersonntag ab. Die Schwierigkeit besteht nun darin, den Ostersonntag zu ermitteln. Ist aber auch kein Problem, denn im Jahr 325 wurde einmal beschlossen, dass der Ostersonntag immer der erste Sonntag nach dem ersten Vollmond im Frühjahr ist. Wem dass jetzt zu kompliziert wird, den kann ich beruhigen. Es gab einmal einen Mathematiker mit Namen Carl Friedrich Gauß, der das Problem 1800 mathematisch beschrieben hat.

Allerdings wurde später noch eine Änderung an der Osterbestimmung gemacht. Und so wurde die Formel zur Osterbestimmung nochmals angepasst.

Um in Programmen die Feiertag zu ermitteln, habe ich mir ein Script geschrieben. Die Programmiersprache ist Python3. Für das Script verwende ich die Zusammenfassung der Formel von Heiner Lichtenberg. Die Berechnung erfolgt im Script nur für den gregorianischen Kalender. 

 

#!/usr/bin/env python
 
#-----------------------------------------------------------------------------#
 
# Mit diesem Script können die Feiertage in Deutschland berechnet und         #
# ausgegeben werden. Wird dem Script ein Bundesland übergeben, werden alle    #
# Feiertage dieses Bundeslandes ausgegeben. Wird kein Bundesland übergeben,   #
# so werden nur die bundeseinheitlichen Feiertage ausgegeben.                 #
#                                                                             #
# Autor: Stephan John                                                         #
# Version: 1.1                                                                #
# Datum: 25.05.2021                                                           #
#                                                                             #
#                                                                             #
#-----------------------------------------------------------------------------#
 
import datetime
 
state_codes = {
 
                'Baden-Württemberg':'BW',
                'Bayern':'BY',
                'Berlin':'BE',
                'Brandenburg':'BB',
                'Bremen':'HB',
                'Hamburg':'HH',
                'Hessen':'HE',
                'Mecklenburg-Vorpommern':'MV',
                'Niedersachsen':'NI',
                'Nordrhein-Westfalen':'NW',
                'Rheinland-Pfalz':'RP',
                'Saarland':'SL',
                'Sachsen':'SN',
                'Sachsen-Anhalt':'ST',
                'Schleswig-Holstein':'SH',
                'Thüringen':'TH',
               }
 
def holidays(year, state=None):
 
    """
    prüft die eingegebenen Werte für die Berechnung der Feiertage
    year  => Jahreszahl ab 1970
    state => Bundesland als offizielle Abkürzung oder Vollname
    """
 
    try:
        year = int(year)
        if year < 1970:
            year = 1970
            print('Jahreszahl wurde auf 1970 geändert')
 
    except ValueError:
        print('Fehlerhafte Angabe der Jahreszahl')
        return
 
    if state:
        if state in list(state_codes.keys()):
            state_code = state_codes[state]
        else:
            if state.upper() in list(state_codes.values()):
                state_code = state.upper()
            else:
                state_code = None
    else:
        state_code = None
    if not state_code:
        print('Es werden nur die deutschlandweit gültigen Feiertage ausgegeben')
 
    hl = Holidays(year, state_code)
    holidays = hl.get_holiday_list()
 
    for h in holidays:
        print(h[1],  h[0])
 
class Holidays:
 
    """
    Berechnet die Feiertage für ein Jahr. Wird ein Bundesland übergeben, werden
    alle Feiertage des Bundeslandes zurückgegeben. Das erfolgt über die
    Funktion get_holiday_list().
    Das Bundesland (state_code) muss mit der offiziellen zweistelligen
    Bezeichnung übergeben werden (z.B. Sachsen mit SN)
 
    Holidays(year(int), [state_code(str)])
    """
 
    def __init__(self, year, state_code):
        self.year = int(year)
        if self.year < 1970:
            self.year = 1970
        if state_code:
            self.state_code = state_code.upper()
            if self.state_code not in list(state_codes.values()):
                self.state_code = None
        easter_day = EasterDay(self.year)
        self.easter_date = easter_day.get_date()
        self.holiday_list = []
        self.general_public_holidays()
        if state_code:
            self.get_three_kings(state_code)
            self.get_assumption_day(state_code)
            self.get_reformation_day(state_code)
            self.get_all_saints_day(state_code)
            self.get_repentance_and_prayer_day(state_code)
            self.get_corpus_christi(state_code)
            self.get_womens_day(state_code)
            self.get_childrens_day(state_code)
            self.get_day_of_liberation(state_code)  # nur für das Jahr 2020 in Berlin
 
    def get_holiday_list(self):
 
        """
        Gibt die Liste mit den Feiertagen zurück
        """
 
        self.holiday_list.sort()
        print(self.holiday_list)
        return self.holiday_list
 
    def general_public_holidays(self):
 
        """
        Alle bundeseinheitlichen Feiertage werden der Feiertagsliste
        zugefügt.
        """
 
        # feste Feiertage:
 
        newyear = datetime.date(self.year, 1, 1)
        self.holiday_list.append([newyear, 'Neujahr'])
        may = datetime.date(self.year, 5, 1)
        self.holiday_list.append([may, '1. Mai'])
        union = datetime.date(self.year, 10, 3)
        self.holiday_list.append([union, 'Tag der deutschen Einheit'])
        christmas1 = datetime.date(self.year, 12, 25)
        self.holiday_list.append([christmas1, 'Erster Weihnachtsfeiertag'])
        christmas2 = datetime.date(self.year, 12, 26)
        self.holiday_list.append([christmas2, 'Zweiter Weihnachtsfeiertag'])
 
        #bewegliche Feiertage:
 
        self.holiday_list.append([self.get_holiday(2, _type='minus'), 'Karfreitag'])
        self.holiday_list.append([self.easter_date, 'Ostersonntag'])
        self.holiday_list.append([self.get_holiday(1), 'Ostermontag'])
        self.holiday_list.append([self.get_holiday(39), 'Christi Himmelfahrt'])
        self.holiday_list.append([self.get_holiday(49), 'Pfingstsonntag'])
        self.holiday_list.append([self.get_holiday(50), 'Pfingstmontag'])
 
 
    def get_holiday(self, days, _type='plus'):
 
        """
        Berechnet anhand des Ostersonntages und der übergebenen Anzahl Tage
        das Datum des gewünschten Feiertages. Mit _type wird bestimmt, ob die Anzahl
        Tage addiert oder subtrahiert wird.
        """
 
        delta = datetime.timedelta(days=days)
        if _type == 'minus':
            return self.easter_date - delta
        else:
            return self.easter_date + delta
 
    def get_three_kings(self, state_code):
        """ Heilige Drei Könige """
        valid = ['BY', 'BW', 'ST']
        if state_code in valid:
            three_kings = datetime.date(self.year, 1, 6)
            self.holiday_list.append([three_kings, 'Heilige Drei Könige'])
 
    def get_assumption_day(self, state_code):
        """ Mariä Himmelfahrt """
        valid = ['BY', 'SL']
        if state_code in valid:
            assumption_day = datetime.date(self.year, 8, 15)
            self.holiday_list.append([assumption_day, 'Mariä Himmelfahrt'])
 
    def get_reformation_day(self, state_code):
        """ Reformationstag """
        valid = ['BB', 'MV', 'SN', 'ST', 'TH', 'HH', 'HB', 'SH', 'NI']
        if state_code in valid:
            reformation_day = datetime.date(self.year, 10, 31)
            self.holiday_list.append([reformation_day, 'Reformationstag'])
 
    def get_all_saints_day(self, state_code):
        """ Allerheiligen """
        valid = ['BW', 'BY', 'NW', 'RP', 'SL']
        if state_code in valid:
            all_saints_day = datetime.date(self.year, 11, 1)
            self.holiday_list.append([all_saints_day, 'Allerheiligen'])
 
    def get_repentance_and_prayer_day(self, state_code):
 
        """
        Buß und Bettag
        (Mittwoch zwischen dem 16. und 22. November)
        """
 
        valid = ['SN']
        if state_code in valid:
            first_possible_day = datetime.date(self.year, 11, 16)
            rap_day = first_possible_day
            weekday = rap_day.weekday()
            step = datetime.timedelta(days=1)
            while weekday != 2:
                rap_day = rap_day + step
                weekday = rap_day.weekday()
            self.holiday_list.append([rap_day, 'Buß und Bettag'])
 
    def get_corpus_christi(self, state_code):
 
        """
        Fronleichnam
        60 Tage nach Ostersonntag
        """
 
        valid = ['BW','BY','HE','NW','RP','SL']
        if state_code in valid:
            corpus_christi = self.get_holiday(60)
 
            self.holiday_list.append([corpus_christi, 'Fronleichnam'])
 
    def get_womens_day(self, state_code):
        """ Frauentag """
        valid = ['BE', ]
        if state_code in valid:
            womens_day = datetime.date(self.year, 3, 8)
            self.holiday_list.append([womens_day, 'Frauentag'])
 
    def get_childrens_day(self, state_code):
        """ Weltkindertag """
        valid = ['TH', ]
        if state_code in valid:
            childrens_day = datetime.date(self.year, 9, 20)
            self.holiday_list.append([childrens_day, 'Weltkindertag'])
 
    def get_day_of_liberation(self, state_code):
        """Tag der Befreiung"""  # nur im Jahr 2020
        valid = ['BE', ]
        if state_code in valid:
            day_of_liberation = datetime.date(2020, 5, 8)
            self.holiday_list.append([day_of_liberation, 'Tag der Befreiung'])
 
 
class EasterDay:
 
    """
    Berechnung des Ostersonntages nach der Formel von Heiner Lichtenberg für
    den gregorianischen Kalender. Diese Formel stellt eine Zusammenfassung der
    Gaußschen Osterformel dar
    Infos unter http://de.wikipedia.org/wiki/Gaußsche_Osterformel
    """
 
    def __init__(self, year):
        self.year = year
 
    def get_k(self):
 
        """
        Säkularzahl:
        K(X) = X div 100
        """
 
        k = self.year // 100
        return k
 
    def get_m(self):
 
        """
        säkulare Mondschaltung:
        M(K) = 15 + (3K + 3) div 4 - (8K + 13) div 25
        """
 
        k = self.get_k()
        m = 15 + (3 * k + 3) // 4 - (8 * k + 13) // 25
        return m
 
    def get_s(self):
 
        """
        säkulare Sonnenschaltung:
        S(K) = 2 - (3K + 3) div 4
        """
 
        k = self.get_k()
        s = 2 - (3 * k + 3) // 4
        return s
 
    def get_a(self):
 
        """
        Mondparameter:
        A(X) = X mod 19
        """
 
        a = self.year % 19
        return a
 
    def get_d(self):
 
        """
        Keim für den ersten Vollmond im Frühling:
        D(A,M) = (19A + M) mod 30
        """
 
        a = self.get_a()
        m = self.get_m()
        d = (19 * a + m) % 30
        return d
 
    def get_r(self):
 
        """
        kalendarische Korrekturgröße:
        R(D,A) = D div 29 + (D div 28 - D div 29) (A div 11)
        """
 
        a = self.get_a()
        d = self.get_d()
        r = d // 29 + (d // 28 - d // 29) * (a // 11)
        return r
 
    def get_og(self):
 
        """
        Ostergrenze:
        OG(D,R) = 21 + D - R
        """
 
        d = self.get_d()
        r = self.get_r()
        og = 21 + d - r
        return og
 
    def get_sz(self):
 
        """
        erster Sonntag im März:
        SZ(X,S) = 7 - (X + X div 4 + S) mod 7
        """
 
        s = self.get_s()
        sz = 7 - (self.year + self.year // 4 + s) % 7
        return sz
 
    def get_oe(self):
 
        """
        Entfernung des Ostersonntags von der Ostergrenze
        (Osterentfernung in Tagen):
        OE(OG,SZ) = 7 - (OG - SZ) mod 7
        """
 
        og = self.get_og()
        sz = self.get_sz()
        oe = 7 - (og - sz) % 7
        return oe
 
    def get_os(self):
 
        """
        das Datum des Ostersonntags als Märzdatum
        (32. März = 1. April usw.):
        OS = OG + OE
        """
 
        og = self.get_og()
        oe = self.get_oe()
        os = og + oe
        return os
 
    def get_date(self):
 
        """
        Ausgabe des Ostersonntags als datetime-Objekt
        """
 
        os = self.get_os()
        if os > 31:
            month = 4
            day = os - 31
        else:
            month = 3
            day = os
        easter_day = datetime.date(int(self.year), int(month), int(day))
        return easter_day
 
if __name__ == '__main__':
    y = input('Bitte geben Sie die Jahreszahl ein: ')
    print('Für die Eingabe eines Bundeslandes folgende Abkürzungen verwenden:')
    print('< leer > um kein Bundesland auszuwählen')
    states = list(state_codes.keys())
    states.sort()
    for l in states:
        print('%s für %s'%(state_codes[l], l))
    s = input('Bitte geben Sie das gewünschte Bundesland ein: ')
    holidays(y, s)

zurück