Create tagerechner_win_v0.12.py
This commit is contained in:
454
CP Tagesrechner Jahresplanung/tagerechner_win_v0.12.py
Normal file
454
CP Tagesrechner Jahresplanung/tagerechner_win_v0.12.py
Normal file
@@ -0,0 +1,454 @@
|
||||
# tagerechner_win_v0_13.py
|
||||
# Zweck: Termine (täglich / wöchentlich / monatlich / quartalsweise) generieren
|
||||
#
|
||||
# v0.13 – Bugfix: Feiertags-Logik greift jetzt GLOBAL
|
||||
# - Fehler behoben, bei dem wöchentliche Termine trotz Checkbox auf Feiertage fielen.
|
||||
# - Wenn "Feiertage beachten" aktiv ist, werden AUCH feste Termine (z.B. "Jeden Freitag")
|
||||
# auf den nächsten Werktag verschoben, falls sie auf einen Feiertag fallen.
|
||||
#
|
||||
# v0.12 – GUI Overhaul (2 Spalten)
|
||||
|
||||
VERSION = "v0.12 (Beta)"
|
||||
|
||||
import re
|
||||
import calendar
|
||||
from datetime import datetime, timedelta, date
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
try:
|
||||
import pyperclip
|
||||
HAS_PYPERCLIP = True
|
||||
except Exception:
|
||||
HAS_PYPERCLIP = False
|
||||
|
||||
|
||||
# ---------- Hilfsfunktionen: Feiertage ----------
|
||||
|
||||
def calculate_easter_sunday(year: int) -> date:
|
||||
a = year % 19
|
||||
b = year // 100
|
||||
c = year % 100
|
||||
d = b // 4
|
||||
e = b % 4
|
||||
f = (b + 8) // 25
|
||||
g = (b - f + 1) // 3
|
||||
h = (19 * a + b - d - g + 15) % 30
|
||||
i = c // 4
|
||||
k = c % 4
|
||||
l = (32 + 2 * e + 2 * i - h - k) % 7
|
||||
m = (a + 11 * h + 22 * l) // 451
|
||||
month = (h + l - 7 * m + 114) // 31
|
||||
day = ((h + l - 7 * m + 114) % 31) + 1
|
||||
return date(year, month, day)
|
||||
|
||||
def is_german_holiday(d: date) -> bool:
|
||||
year = d.year
|
||||
fixed_holidays = [(1, 1), (5, 1), (10, 3), (12, 25), (12, 26)]
|
||||
if (d.month, d.day) in fixed_holidays:
|
||||
return True
|
||||
|
||||
easter_sun = calculate_easter_sunday(year)
|
||||
# Karfreitag (-2), Ostermontag (+1), Himmelfahrt (+39), Pfingstmontag (+50)
|
||||
offsets = [-2, 1, 39, 50]
|
||||
# offsets.append(60) # Optional: Fronleichnam
|
||||
|
||||
for off in offsets:
|
||||
if d == easter_sun + timedelta(days=off):
|
||||
return True
|
||||
return False
|
||||
|
||||
# ---------- Hilfsfunktionen: Format & Logik ----------
|
||||
|
||||
def validate_time_format(time_str: str) -> str:
|
||||
if not re.match(r'^\d{2}:\d{2}$', time_str):
|
||||
raise ValueError("Uhrzeit bitte als HH:MM angeben.")
|
||||
h, m = map(int, time_str.split(":"))
|
||||
if not (0 <= h < 24 and 0 <= m < 60):
|
||||
raise ValueError("Uhrzeit ungültig.")
|
||||
return f"{h:02d}:{m:02d}:00"
|
||||
|
||||
def nth_weekday_in_month(year: int, month: int, n: int, weekday0: int):
|
||||
first_weekday, days_in_month = calendar.monthrange(year, month)
|
||||
delta = (weekday0 - first_weekday) % 7
|
||||
day = 1 + delta + 7 * (n - 1)
|
||||
if day > days_in_month: return None
|
||||
return date(year, month, day)
|
||||
|
||||
def format_full_output(d: date, time_hhmmss: str) -> str:
|
||||
w_names = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
|
||||
return f"{w_names[d.weekday()]}. {d.strftime('%d.%m.%Y')} {time_hhmmss}"
|
||||
|
||||
def format_clean_output(d: date, time_hhmmss: str) -> str:
|
||||
return f"{d.strftime('%d.%m.%Y')} {time_hhmmss}"
|
||||
|
||||
def iso_week_number(d: date) -> int:
|
||||
iso = d.isocalendar()
|
||||
return getattr(iso, "week", iso[1])
|
||||
|
||||
def week_parity_ok(d: date, parity: str | None) -> bool:
|
||||
if parity is None: return True
|
||||
w = iso_week_number(d)
|
||||
return (w % 2) == 1 if parity == 'odd' else (w % 2) == 0
|
||||
|
||||
def next_workday(d: date, check_holidays: bool) -> date:
|
||||
"""Verschiebt Datum auf den nächsten Werktag (Mo-Fr), der kein Feiertag ist."""
|
||||
while d.weekday() >= 5 or (check_holidays and is_german_holiday(d)):
|
||||
d += timedelta(days=1)
|
||||
return d
|
||||
|
||||
def ensure_valid_date(d: date, check_holidays: bool) -> date:
|
||||
"""
|
||||
Wrapper: Prüft, ob d zulässig ist.
|
||||
Wenn check_holidays=True und d ein Feiertag ist -> Verschieben auf nächsten Werktag.
|
||||
(Wochenenden bleiben bei festen Terminen oft erhalten, außer es ist explizit next_workday gewünscht.
|
||||
Aber Feiertage sollten bei 'check_holidays' verschoben werden.)
|
||||
"""
|
||||
if check_holidays and is_german_holiday(d):
|
||||
return next_workday(d, check_holidays=True)
|
||||
return d
|
||||
|
||||
# ---------- Kernlogik ----------
|
||||
|
||||
def generate_dates(year: int, frequency: str, time_hhmmss: str,
|
||||
check_holidays: bool,
|
||||
weekday_for_weekly=None, weekly_iso_week_parity=None,
|
||||
monthly_option=None, day_of_month=None, week_in_month=None,
|
||||
weekday_for_monthly=None, target_weekday_for_monthly=None,
|
||||
relative_week_offset=0, monthly_iso_week_parity=None,
|
||||
quarterly_option=None) -> list[tuple]:
|
||||
|
||||
start_date = date(year, 1, 1)
|
||||
end_date = date(year, 12, 31)
|
||||
raw_dates = []
|
||||
|
||||
if frequency == 't':
|
||||
cur = start_date
|
||||
while cur <= end_date:
|
||||
# Auch täglich sollte an Feiertagen springen, wenn gewünscht?
|
||||
# Interpretation: Täglich ist täglich. Aber wenn check_holidays an ist, skippen wir Feiertage?
|
||||
# Konsistenter: Wir verschieben nicht (sonst Doppelung), wir skippen bei 't'?
|
||||
# Change Management Logik: Tägliche Meetings fallen an Feiertagen aus.
|
||||
if not (check_holidays and is_german_holiday(cur)):
|
||||
raw_dates.append(cur)
|
||||
cur += timedelta(days=1)
|
||||
|
||||
elif frequency == 'w':
|
||||
wk = (weekday_for_weekly - 1) % 7
|
||||
delta = (wk - start_date.weekday()) % 7
|
||||
cur = start_date + timedelta(days=delta)
|
||||
|
||||
# Initiale Paritäts-Suche
|
||||
if weekly_iso_week_parity in ('odd', 'even'):
|
||||
while cur <= end_date and not week_parity_ok(cur, weekly_iso_week_parity):
|
||||
cur += timedelta(days=7)
|
||||
step = 14
|
||||
else:
|
||||
step = 7
|
||||
|
||||
while cur <= end_date:
|
||||
if week_parity_ok(cur, weekly_iso_week_parity):
|
||||
# NEU: Hier prüfen wir auf Feiertag und verschieben ggf.
|
||||
actual_date = ensure_valid_date(cur, check_holidays)
|
||||
raw_dates.append(actual_date)
|
||||
cur += timedelta(days=step)
|
||||
|
||||
elif frequency == 'm':
|
||||
cur = start_date.replace(day=1)
|
||||
while cur <= end_date:
|
||||
d = None
|
||||
if monthly_option == 1: # Fester Tag
|
||||
days_in_month = calendar.monthrange(cur.year, cur.month)[1]
|
||||
if 1 <= day_of_month <= days_in_month:
|
||||
d = date(cur.year, cur.month, day_of_month)
|
||||
# NEU: Check
|
||||
d = ensure_valid_date(d, check_holidays)
|
||||
|
||||
elif monthly_option == 2: # Wiederkehrender Wochentag
|
||||
wk2 = (weekday_for_monthly - 1) % 7
|
||||
d = nth_weekday_in_month(cur.year, cur.month, week_in_month, wk2)
|
||||
if d and not week_parity_ok(d, monthly_iso_week_parity): d = None
|
||||
# NEU: Check
|
||||
if d: d = ensure_valid_date(d, check_holidays)
|
||||
|
||||
elif monthly_option == 3: # Kalendertag -> Werktag (hatte Logik schon, aber sicherheitshalber)
|
||||
days_in_month = calendar.monthrange(cur.year, cur.month)[1]
|
||||
if 1 <= day_of_month <= days_in_month:
|
||||
d = date(cur.year, cur.month, day_of_month)
|
||||
d = next_workday(d, check_holidays) # Hier war es schon korrekt
|
||||
if d.month != cur.month: d = None
|
||||
|
||||
elif monthly_option == 4: # 1. Werktag
|
||||
d = first_workday_of_month(cur.year, cur.month, check_holidays) # Hier war es schon korrekt
|
||||
|
||||
elif monthly_option == 5: # Relativ
|
||||
base_wk = (weekday_for_monthly - 1) % 7
|
||||
target_wk = (target_weekday_for_monthly - 1) % 7
|
||||
ref = nth_weekday_in_month(cur.year, cur.month, week_in_month, base_wk)
|
||||
if ref:
|
||||
delta_days = (target_wk - base_wk) % 7
|
||||
d = ref + timedelta(days=delta_days + 7 * relative_week_offset)
|
||||
if d.month != cur.month: d = None
|
||||
# NEU: Check
|
||||
if d: d = ensure_valid_date(d, check_holidays)
|
||||
|
||||
if d and d <= end_date:
|
||||
raw_dates.append(d)
|
||||
|
||||
if cur.month == 12: cur = date(cur.year + 1, 1, 1)
|
||||
else: cur = date(cur.year, cur.month + 1, 1)
|
||||
|
||||
elif frequency == 'q':
|
||||
quarters = [3, 6, 9, 12]
|
||||
for m in quarters:
|
||||
days_in_month = calendar.monthrange(year, m)[1]
|
||||
d = None
|
||||
if quarterly_option == 1:
|
||||
d = date(year, m, days_in_month)
|
||||
elif quarterly_option == 2:
|
||||
day = min(day_of_month, days_in_month)
|
||||
d = date(year, m, day)
|
||||
|
||||
# NEU: Auch Quartals-Termine verschieben bei Feiertag
|
||||
if d:
|
||||
d = ensure_valid_date(d, check_holidays)
|
||||
raw_dates.append(d)
|
||||
|
||||
# Formatierung
|
||||
final_list = []
|
||||
# Sortieren zur Sicherheit, falls durch Verschiebungen was durcheinander gerät (z.B. Fr -> Mo, aber Mo Termin existiert)
|
||||
raw_dates.sort()
|
||||
|
||||
for d in raw_dates:
|
||||
disp = format_full_output(d, time_hhmmss)
|
||||
clip = format_clean_output(d, time_hhmmss)
|
||||
final_list.append((disp, clip))
|
||||
|
||||
return final_list
|
||||
|
||||
# ---------- GUI ----------
|
||||
|
||||
class App(tk.Tk):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.title(f"Termin-Generator – Change-Planung {VERSION}")
|
||||
self.geometry("1050x650")
|
||||
self.minsize(900, 600)
|
||||
|
||||
self.left_frame = ttk.Frame(self, padding="10")
|
||||
self.left_frame.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
self.right_frame = ttk.LabelFrame(self, text="Ergebnis", padding="10")
|
||||
self.right_frame.grid(row=0, column=1, sticky="nsew", padx=(0, 10), pady=10)
|
||||
|
||||
self.grid_columnconfigure(1, weight=1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
|
||||
# LINKS
|
||||
self.general_frame = ttk.LabelFrame(self.left_frame, text="Allgemeines", padding="10")
|
||||
self.general_frame.pack(fill="x", pady=(0, 10))
|
||||
|
||||
ttk.Label(self.general_frame, text="Jahr:").grid(row=0, column=0, sticky="w", pady=5)
|
||||
self.year_var = tk.IntVar(value=datetime.now().year)
|
||||
ttk.Spinbox(self.general_frame, from_=2020, to=2030, textvariable=self.year_var, width=6).grid(row=0, column=1, sticky="w", padx=5)
|
||||
|
||||
self.check_holidays_var = tk.BooleanVar(value=True)
|
||||
ttk.Checkbutton(self.general_frame, text="🇩🇪 Feiertage beachten", variable=self.check_holidays_var).grid(row=0, column=2, sticky="w", padx=15)
|
||||
|
||||
ttk.Label(self.general_frame, text="Uhrzeit:").grid(row=1, column=0, sticky="w", pady=5)
|
||||
self.time_var = tk.StringVar(value="00:00")
|
||||
ttk.Entry(self.general_frame, textvariable=self.time_var, width=8).grid(row=1, column=1, sticky="w", padx=5)
|
||||
|
||||
ttk.Label(self.general_frame, text="Turnus:").grid(row=2, column=0, sticky="w", pady=5)
|
||||
self.frequency_var = tk.StringVar(value="📆 Monatlich")
|
||||
freq_cb = ttk.Combobox(self.general_frame, textvariable=self.frequency_var, state='readonly', width=35,
|
||||
values=["🔁 Täglich", "📅 Wöchentlich", "📆 Monatlich", "🏣 Quartalsweise"])
|
||||
freq_cb.grid(row=2, column=1, columnspan=2, sticky="w", padx=5)
|
||||
freq_cb.bind('<<ComboboxSelected>>', self._toggle_by_frequency)
|
||||
|
||||
self.dynamic_container = ttk.Frame(self.left_frame)
|
||||
self.dynamic_container.pack(fill="both", expand=True)
|
||||
|
||||
# Weekly
|
||||
self.weekly_frame = ttk.LabelFrame(self.dynamic_container, text="Wöchentliche Optionen", padding="10")
|
||||
ttk.Label(self.weekly_frame, text="Wochentag:").grid(row=0, column=0, sticky="w", pady=5)
|
||||
self.weekly_weekday_var = tk.IntVar(value=1)
|
||||
self.wd_combo = ttk.Combobox(self.weekly_frame, textvariable=self.weekly_weekday_var, state="readonly", width=15,
|
||||
values=["1 (Mo)", "2 (Di)", "3 (Mi)", "4 (Do)", "5 (Fr)", "6 (Sa)", "7 (So)"])
|
||||
self.wd_combo.current(0)
|
||||
self.wd_combo.grid(row=0, column=1, sticky="w", padx=5)
|
||||
|
||||
ttk.Label(self.weekly_frame, text="Filter:").grid(row=1, column=0, sticky="w", pady=5)
|
||||
self.weekly_iso_kw_var = tk.StringVar(value='Keine Einschränkung')
|
||||
ttk.Combobox(self.weekly_frame, textvariable=self.weekly_iso_kw_var, state="readonly", width=25,
|
||||
values=['Keine Einschränkung', 'Nur ungerade KW', 'Nur gerade KW']).grid(row=1, column=1, sticky="w", padx=5)
|
||||
|
||||
# Monthly
|
||||
self.monthly_frame = ttk.LabelFrame(self.dynamic_container, text="Monatliche Optionen", padding="10")
|
||||
self.monthly_option_var = tk.IntVar(value=2)
|
||||
opts = [(1, "Fester Kalendertag"), (2, "Wiederkehrender Wochentag"), (3, "Kalendertag (Werktag-Verschiebung)"), (4, "Erster Werktag des Monats"), (5, "Relativ zu anderem Datum")]
|
||||
for val, txt in opts:
|
||||
ttk.Radiobutton(self.monthly_frame, text=txt, variable=self.monthly_option_var, value=val, command=self._toggle_monthly).pack(anchor="w", pady=2)
|
||||
|
||||
self.monthly_params_frame = ttk.Frame(self.monthly_frame, padding=(0,10))
|
||||
self.monthly_params_frame.pack(fill="x")
|
||||
self.lbl_dom = ttk.Label(self.monthly_params_frame, text="Tag (1-31):")
|
||||
self.spin_dom = ttk.Spinbox(self.monthly_params_frame, from_=1, to=31, width=5)
|
||||
self.lbl_wim = ttk.Label(self.monthly_params_frame, text="Vorkommen (1-5):")
|
||||
self.spin_wim = ttk.Spinbox(self.monthly_params_frame, from_=1, to=5, width=5)
|
||||
self.lbl_wd = ttk.Label(self.monthly_params_frame, text="Wochentag (1-7):")
|
||||
self.spin_wd = ttk.Spinbox(self.monthly_params_frame, from_=1, to=7, width=5)
|
||||
self.lbl_twd = ttk.Label(self.monthly_params_frame, text="Ziel-Tag (1-7):")
|
||||
self.spin_twd = ttk.Spinbox(self.monthly_params_frame, from_=1, to=7, width=5)
|
||||
self.lbl_off = ttk.Label(self.monthly_params_frame, text="Offset (Wochen):")
|
||||
self.spin_off = ttk.Spinbox(self.monthly_params_frame, from_=-4, to=4, width=5)
|
||||
self.spin_off.set(0)
|
||||
self.lbl_kw = ttk.Label(self.monthly_params_frame, text="KW-Filter:")
|
||||
self.cb_kw = ttk.Combobox(self.monthly_params_frame, values=['Kein', 'Ungerade', 'Gerade'], state="readonly", width=10)
|
||||
self.cb_kw.current(0)
|
||||
|
||||
# Quarterly
|
||||
self.quarterly_frame = ttk.LabelFrame(self.dynamic_container, text="Quartals-Optionen", padding="10")
|
||||
self.quarterly_option_var = tk.IntVar(value=1)
|
||||
ttk.Radiobutton(self.quarterly_frame, text="Letzter Tag des Quartals", variable=self.quarterly_option_var, value=1, command=self._toggle_quarterly).pack(anchor="w")
|
||||
ttk.Radiobutton(self.quarterly_frame, text="Fester Tag im Quartalsmonat", variable=self.quarterly_option_var, value=2, command=self._toggle_quarterly).pack(anchor="w")
|
||||
self.quart_day_frame = ttk.Frame(self.quarterly_frame)
|
||||
self.quart_day_frame.pack(fill="x", pady=5)
|
||||
ttk.Label(self.quart_day_frame, text="Tag:").pack(side="left")
|
||||
self.spin_q_dom = ttk.Spinbox(self.quart_day_frame, from_=1, to=31, width=5)
|
||||
self.spin_q_dom.pack(side="left", padx=5)
|
||||
self.spin_q_dom.set(15)
|
||||
|
||||
self.preview_var = tk.StringVar(value="...")
|
||||
ttk.Label(self.left_frame, textvariable=self.preview_var, foreground="blue", wraplength=300).pack(side="bottom", fill="x", pady=10)
|
||||
|
||||
# RECHTS
|
||||
btn_frame = ttk.Frame(self.right_frame)
|
||||
btn_frame.pack(fill="x", pady=(0, 10))
|
||||
self.run_btn = ttk.Button(btn_frame, text="🚀 Berechnen & Kopieren", command=self.on_run)
|
||||
self.run_btn.pack(side="left", fill="x", expand=True, padx=(0, 5))
|
||||
self.clear_btn = ttk.Button(btn_frame, text="🧹", width=3, command=self.on_clear)
|
||||
self.clear_btn.pack(side="right")
|
||||
|
||||
list_container = ttk.Frame(self.right_frame)
|
||||
list_container.pack(fill="both", expand=True)
|
||||
self.scrollbar = ttk.Scrollbar(list_container, orient="vertical")
|
||||
self.result_list = tk.Listbox(list_container, yscrollcommand=self.scrollbar.set, font=("Consolas", 10))
|
||||
self.scrollbar.config(command=self.result_list.yview)
|
||||
self.scrollbar.pack(side="right", fill="y")
|
||||
self.result_list.pack(side="left", fill="both", expand=True)
|
||||
|
||||
self.status_var = tk.StringVar(value="Bereit.")
|
||||
self.status_bar = ttk.Label(self, textvariable=self.status_var, relief="sunken", anchor="w", padding=(5, 2))
|
||||
self.status_bar.grid(row=1, column=0, columnspan=2, sticky="ew")
|
||||
|
||||
self._toggle_by_frequency(None)
|
||||
self._toggle_monthly()
|
||||
self._toggle_quarterly()
|
||||
|
||||
def _get_frequency_code(self) -> str:
|
||||
val = self.frequency_var.get()
|
||||
if "Täglich" in val: return 't'
|
||||
if "Wöchentlich" in val: return 'w'
|
||||
if "Monatlich" in val: return 'm'
|
||||
if "Quartals" in val: return 'q'
|
||||
return 't'
|
||||
|
||||
def _toggle_by_frequency(self, _evt):
|
||||
f = self._get_frequency_code()
|
||||
self.weekly_frame.pack_forget()
|
||||
self.monthly_frame.pack_forget()
|
||||
self.quarterly_frame.pack_forget()
|
||||
if f == 'w': self.weekly_frame.pack(fill="x", pady=10)
|
||||
elif f == 'm': self.monthly_frame.pack(fill="x", pady=10)
|
||||
elif f == 'q': self.quarterly_frame.pack(fill="x", pady=10)
|
||||
self._update_preview()
|
||||
|
||||
def _clear_monthly_params(self):
|
||||
for widget in self.monthly_params_frame.winfo_children(): widget.grid_forget()
|
||||
|
||||
def _toggle_monthly(self):
|
||||
self._clear_monthly_params()
|
||||
opt = self.monthly_option_var.get()
|
||||
if opt in (1, 3):
|
||||
self.lbl_dom.grid(row=0, column=0, padx=5); self.spin_dom.grid(row=0, column=1, padx=5)
|
||||
elif opt == 2:
|
||||
self.lbl_wim.grid(row=0, column=0); self.spin_wim.grid(row=0, column=1)
|
||||
self.lbl_wd.grid(row=0, column=2); self.spin_wd.grid(row=0, column=3)
|
||||
self.lbl_kw.grid(row=1, column=0); self.cb_kw.grid(row=1, column=1)
|
||||
elif opt == 5:
|
||||
self.lbl_wim.grid(row=0, column=0); self.spin_wim.grid(row=0, column=1)
|
||||
self.lbl_wd.grid(row=0, column=2); self.spin_wd.grid(row=0, column=3)
|
||||
self.lbl_twd.grid(row=1, column=0); self.spin_twd.grid(row=1, column=1)
|
||||
self.lbl_off.grid(row=1, column=2); self.spin_off.grid(row=1, column=3)
|
||||
self._update_preview()
|
||||
|
||||
def _toggle_quarterly(self):
|
||||
if self.quarterly_option_var.get() == 2:
|
||||
for child in self.quart_day_frame.winfo_children(): child.configure(state='normal')
|
||||
else:
|
||||
for child in self.quart_day_frame.winfo_children(): child.configure(state='disabled')
|
||||
self._update_preview()
|
||||
|
||||
def _update_preview(self):
|
||||
self.preview_var.set(f"Modus: {self.frequency_var.get()}")
|
||||
|
||||
def on_run(self):
|
||||
try:
|
||||
self.status_var.set("Berechne...")
|
||||
self.update_idletasks()
|
||||
|
||||
year = self.year_var.get()
|
||||
freq = self._get_frequency_code()
|
||||
time_s = validate_time_format(self.time_var.get())
|
||||
check_holidays = self.check_holidays_var.get()
|
||||
|
||||
wd_weekly = int(self.wd_combo.get().split()[0])
|
||||
kw_par = None
|
||||
if "ungerade" in self.weekly_iso_kw_var.get().lower(): kw_par = 'odd'
|
||||
elif "gerade" in self.weekly_iso_kw_var.get().lower(): kw_par = 'even'
|
||||
|
||||
m_opt = self.monthly_option_var.get()
|
||||
dom = int(self.spin_dom.get() or 1)
|
||||
wim = int(self.spin_wim.get() or 1)
|
||||
wd_monthly = int(self.spin_wd.get() or 1)
|
||||
twd = int(self.spin_twd.get() or 1)
|
||||
off = int(self.spin_off.get() or 0)
|
||||
m_kw_par = None
|
||||
if "Ungerade" in self.cb_kw.get(): m_kw_par = 'odd'
|
||||
elif "Gerade" in self.cb_kw.get(): m_kw_par = 'even'
|
||||
|
||||
q_opt = self.quarterly_option_var.get()
|
||||
q_dom = int(self.spin_q_dom.get() or 1)
|
||||
|
||||
results = generate_dates(
|
||||
year, freq, time_s, check_holidays,
|
||||
weekday_for_weekly=wd_weekly, weekly_iso_week_parity=kw_par,
|
||||
monthly_option=m_opt, day_of_month=dom, week_in_month=wim,
|
||||
weekday_for_monthly=wd_monthly, target_weekday_for_monthly=twd,
|
||||
relative_week_offset=off, monthly_iso_week_parity=m_kw_par,
|
||||
quarterly_option=q_opt if freq == 'q' else None
|
||||
)
|
||||
if freq == 'q' and q_opt == 2:
|
||||
results = generate_dates(year, freq, time_s, check_holidays, quarterly_option=q_opt, day_of_month=q_dom)
|
||||
|
||||
self.result_list.delete(0, tk.END)
|
||||
clipboard_text = []
|
||||
for display_str, clip_str in results:
|
||||
self.result_list.insert(tk.END, display_str)
|
||||
clipboard_text.append(clip_str)
|
||||
|
||||
if HAS_PYPERCLIP:
|
||||
pyperclip.copy("\n".join(clipboard_text))
|
||||
self.status_var.set(f"✅ {len(results)} Termine generiert & kopiert!")
|
||||
else:
|
||||
self.status_var.set(f"✅ {len(results)} Termine generiert (Kein Clipboard-Modul).")
|
||||
except Exception as e:
|
||||
self.status_var.set(f"❌ Fehler: {e}")
|
||||
|
||||
def on_clear(self):
|
||||
self.result_list.delete(0, tk.END)
|
||||
self.status_var.set("Liste geleert.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
App().mainloop()
|
||||
Reference in New Issue
Block a user