import holidays from datetime import date, timedelta, datetime class BusinessCalendar: """ Handles business day calculations, considering weekends and holidays (specifically for Bavaria/Germany). """ def __init__(self, country='DE', state='BY'): # Initialize holidays for Germany, Bavaria self.holidays = holidays.country_holidays(country, subdiv=state) def is_business_day(self, check_date: date) -> bool: """ Checks if a given date is a business day (Mon-Fri) and not a holiday. """ # Check for weekend (Saturday=5, Sunday=6) if check_date.weekday() >= 5: return False # Check for holiday if check_date in self.holidays: return False return True def get_next_business_day(self, start_date: date) -> date: """ Returns the next valid business day starting from (and including) start_date. If start_date is a business day, it is returned. Otherwise, it searches forward. """ current_date = start_date # Safety limit to prevent infinite loops in case of misconfiguration # (though 365 days of holidays is unlikely) for _ in range(365): if self.is_business_day(current_date): return current_date current_date += timedelta(days=1) return current_date def get_next_send_time(self, scheduled_time: datetime) -> datetime: """ Calculates the next valid timestamp for sending emails. If scheduled_time falls on a holiday or weekend, it moves to the next business day at the same time. """ original_date = scheduled_time.date() next_date = self.get_next_business_day(original_date) if next_date == original_date: return scheduled_time # Combine the new date with the original time return datetime.combine(next_date, scheduled_time.time()) # Example usage for testing if __name__ == "__main__": calendar = BusinessCalendar() # Test dates dates_to_test = [ date(2026, 5, 1), # Holiday (Labor Day) date(2026, 12, 25), # Holiday (Christmas) date(2026, 4, 6), # Holiday (Easter Monday 2026) date(2026, 2, 10), # Likely a Tuesday (Business Day) date(2026, 2, 14) # Saturday ] print("--- Business Day Check (Bayern 2026) ---") for d in dates_to_test: is_biz = calendar.is_business_day(d) next_biz = calendar.get_next_business_day(d) holiday_name = calendar.holidays.get(d) if d in calendar.holidays else "" status = "✅ Business Day" if is_biz else f"❌ Blocked ({holiday_name if holiday_name else 'Weekend'})" print(f"Date: {d} | {status} -> Next: {next_biz}")