From e2a5c1a3fa58827c82df32304f05b437bd53d107 Mon Sep 17 00:00:00 2001 From: elpatron Date: Fri, 25 Jul 2025 17:10:57 +0200 Subject: [PATCH] =?UTF-8?q?Feat:=20Bundesland-Feiertage=20f=C3=BCr=20Werkt?= =?UTF-8?q?agsberechnung=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neue Funktion zur Abfrage bundeslandspezifischer Feiertage über feiertage-api.de - Werktagsberechnung berücksichtigt jetzt optional Feiertage des gewählten Bundeslandes - Frontend: Dropdown für Bundesland-Auswahl (nur aktiv wenn Werktage-Checkbox aktiviert) - Anzeige der Anzahl Wochenendtage und Feiertage im Ergebnis - REST API erweitert um bundesland-Parameter - README.md aktualisiert mit Dokumentation der neuen Funktion --- README.md | 33 ++++++++++++++++++++++++++--- app.py | 38 ++++++++++++++++++++++++++++++---- requirements.txt | 3 ++- templates/index.html | 49 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 114 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 38922f8..5fb123c 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Datumsrechner Live: [https://date.elpatron.me](https://date.elpatron.me) ## Funktionen - Anzahl der Tage zwischen zwei Daten -- Anzahl der Werktage zwischen zwei Daten +- Anzahl der Werktage zwischen zwei Daten (mit optionaler Berücksichtigung bundeslandspezifischer Feiertage) - Anzeige des Wochentags eines Datums - Datum plus/minus X Tage - Datum plus/minus X Werktage @@ -50,6 +50,30 @@ Datumsrechner Live: [https://date.elpatron.me](https://date.elpatron.me) - Start-/Enddatum einer Kalenderwoche eines Jahres - Statistik-Dashboard mit Passwortschutz unter `/stats` +## Bundesland-Feiertage + +Die Werktagsberechnung kann optional bundeslandspezifische Feiertage berücksichtigen. Dazu wird die kostenlose API von [feiertage-api.de](https://feiertage-api.de) verwendet. + +**Verfügbare Bundesländer:** +- 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) + +Die Feiertage werden automatisch für den gewählten Zeitraum abgerufen und bei der Werktagsberechnung als arbeitsfreie Tage behandelt. Im Ergebnis werden zusätzlich die Anzahl der Wochenendtage und Feiertage angezeigt. + ## Installation (lokal) 1. Python 3.8+ installieren @@ -148,7 +172,8 @@ Alle Datumsfunktionen stehen auch als REST-API zur Verfügung. Die API akzeptier { "start": "2024-06-01", "end": "2024-06-10", - "werktage": true + "werktage": true, + "bundesland": "BY" } ``` @@ -156,7 +181,7 @@ Alle Datumsfunktionen stehen auch als REST-API zur Verfügung. Die API akzeptier ```bash curl -X POST http://localhost:5000/api/tage_werktage \ -H "Content-Type: application/json" \ - -d '{"start": "2024-06-01", "end": "2024-06-10", "werktage": true}' + -d '{"start": "2024-06-01", "end": "2024-06-10", "werktage": true, "bundesland": "BY"}' ``` **Antwort:** @@ -164,6 +189,8 @@ curl -X POST http://localhost:5000/api/tage_werktage \ { "result": 7 } ``` +**Hinweis:** Der Parameter `bundesland` ist optional und wird nur bei `"werktage": true` berücksichtigt. Verfügbare Bundesland-Kürzel siehe oben. + #### 2. Wochentag zu einem Datum **POST** `/api/wochentag` diff --git a/app.py b/app.py index 8fc91fb..8c498c6 100644 --- a/app.py +++ b/app.py @@ -4,6 +4,7 @@ import numpy as np from dateutil.relativedelta import relativedelta import os import time +import requests app_start_time = time.time() @@ -14,6 +15,18 @@ app.secret_key = os.environ.get('SECRET_KEY', 'dev-key') WOCHENTAGE = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"] +def get_feiertage(year, bundesland): + """Holt die Feiertage für ein Jahr und Bundesland von feiertage-api.de.""" + url = f"https://feiertage-api.de/api/?jahr={year}&nur_land={bundesland}" + try: + resp = requests.get(url, timeout=5) + data = resp.json() + # Die API gibt ein Dict mit Feiertagsnamen als Key, jeweils mit 'datum' als Wert + return [v['datum'] for v in data.values() if 'datum' in v] + except Exception as e: + print(f"Fehler beim Abrufen der Feiertage: {e}") + return [] + @app.route('/', methods=['GET', 'POST']) def index(): # Rudimentäres Logging für Page Impressions @@ -40,6 +53,7 @@ def index(): from datetime import datetime as dt f.write(f"{dt.now().isoformat()} PAGEVIEW\n") tage = werktage = wochentag = datumsrechnung = werktagsrechnung = kw_berechnen = kw_datum = wochen_monate = None + feiertage_anzahl = wochenendtage_anzahl = None active_idx = 0 plusminus_result = None if request.method == 'POST': @@ -53,13 +67,27 @@ def index(): start = request.form.get('start1') end = request.form.get('end1') is_werktage = request.form.get('werktage') in ('on', 'true', '1', True) + bundesland = request.form.get('bundesland') try: d1 = datetime.strptime(start, '%Y-%m-%d') d2 = datetime.strptime(end, '%Y-%m-%d') + if d1 > d2: + d1, d2 = d2, d1 + # Feiertage bestimmen + holidays = [] + if bundesland: + years = set([d1.year, d2.year]) + for y in years: + holidays.extend(get_feiertage(y, bundesland)) + # Alle Tage im Bereich + all_days = [(d1 + timedelta(days=i)).date() for i in range((d2 - d1).days + 1)] + # Wochenendtage zählen + wochenendtage_anzahl = sum(1 for d in all_days if d.weekday() >= 5) + # Feiertage zählen (nur die, die im Bereich liegen und nicht auf Wochenende fallen) + feiertage_im_zeitraum = [f for f in holidays if d1.date() <= datetime.strptime(f, '%Y-%m-%d').date() <= d2.date()] + feiertage_anzahl = sum(1 for f in feiertage_im_zeitraum if datetime.strptime(f, '%Y-%m-%d').date().weekday() < 5) if is_werktage: - if d1 > d2: - d1, d2 = d2, d1 - tage = np.busday_count(d1.date(), (d2 + timedelta(days=1)).date()) + tage = np.busday_count(d1.date(), (d2 + timedelta(days=1)).date(), holidays=holidays) else: tage = abs((d2 - d1).days) except Exception: @@ -129,7 +157,9 @@ def index(): plusminus_result = f"Datum {d.strftime('%d.%m.%Y')} {'plus' if anzahl_int>=0 else 'minus'} {abs(anzahl_int)} Monate: {result.strftime('%d.%m.%Y')}" except Exception: plusminus_result = 'Ungültige Eingabe' - return render_template('index.html', tage=tage, werktage=werktage, wochentag=wochentag, plusminus_result=plusminus_result, kw_berechnen=kw_berechnen, kw_datum=kw_datum, active_idx=active_idx) + return render_template('index.html', tage=tage, werktage=werktage, wochentag=wochentag, plusminus_result=plusminus_result, kw_berechnen=kw_berechnen, kw_datum=kw_datum, active_idx=active_idx + , feiertage_anzahl=feiertage_anzahl, wochenendtage_anzahl=wochenendtage_anzahl + ) def parse_log_stats(log_path): diff --git a/requirements.txt b/requirements.txt index ef4362c..26c313b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ Flask==3.0.3 numpy==1.26.4 python-dateutil==2.9.0.post0 -pytest==8.2.2 \ No newline at end of file +pytest==8.2.2 +requests \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 6f29234..e79036b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -604,16 +604,49 @@ footer br + a { Optionen + {% if tage is not none %}
{% if request.form.get('werktage') %} - Anzahl der Werktage zwischen {{ format_date(request.form.get('start1', '')) }} und {{ format_date(request.form.get('end1', '')) }}: {{ tage }} + Anzahl der Werktage zwischen {{ format_date(request.form.get('start1', '')) }} und {{ format_date(request.form.get('end1', '')) }}{% if request.form.get('bundesland') %} (Feiertage {{ request.form.get('bundesland') }}){% endif %}: {{ tage }} {% else %} Anzahl der Tage zwischen {{ format_date(request.form.get('start1', '')) }} und {{ format_date(request.form.get('end1', '')) }}: {{ tage }} {% endif %} + {% if wochenendtage_anzahl is not none or feiertage_anzahl is not none %} +
+ + {% if wochenendtage_anzahl is not none %} + {{ wochenendtage_anzahl }} Wochenendtage + {% endif %} + {% if feiertage_anzahl is not none %} + {% if wochenendtage_anzahl is not none %} | {% endif %} + {{ feiertage_anzahl }} Feiertage (Mo-Fr{% if request.form.get('bundesland') %}, {{ request.form.get('bundesland') }}{% endif %}) + {% endif %} + + {% endif %}
{% endif %} @@ -788,5 +821,19 @@ footer br + a { REST API Dokumentation (Swagger)
© 2025 M. Busche + \ No newline at end of file