Features: - Manager-centric parking spot management - Fair assignment algorithm (parking/presence ratio) - Presence tracking calendar - Closing days (specific & weekly recurring) - Guarantees and exclusions - Authelia/LLDAP integration for SSO Stack: - FastAPI backend - SQLite database - Vanilla JS frontend - Docker deployment
117 lines
3.0 KiB
Python
117 lines
3.0 KiB
Python
"""
|
|
Holiday Service
|
|
Configurable holiday calculation for different regions
|
|
|
|
Currently supports Italian holidays. Can be extended to support other regions
|
|
by adding new holiday sets and a configuration option.
|
|
"""
|
|
from datetime import datetime, date, timedelta
|
|
|
|
|
|
def calculate_easter(year: int) -> date:
|
|
"""Calculate Easter Sunday using the Computus algorithm"""
|
|
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 get_easter_monday(year: int) -> date:
|
|
"""Get Easter Monday for a given year"""
|
|
easter = calculate_easter(year)
|
|
return easter + timedelta(days=1)
|
|
|
|
|
|
# Italian fixed holidays (month, day)
|
|
ITALIAN_FIXED_HOLIDAYS = [
|
|
(1, 1), # New Year's Day
|
|
(1, 6), # Epiphany
|
|
(4, 25), # Liberation Day
|
|
(5, 1), # Labour Day
|
|
(6, 2), # Republic Day
|
|
(8, 15), # Assumption
|
|
(11, 1), # All Saints
|
|
(12, 8), # Immaculate Conception
|
|
(12, 25), # Christmas
|
|
(12, 26), # St. Stephen's
|
|
]
|
|
|
|
|
|
def is_italian_holiday(check_date: date | datetime) -> bool:
|
|
"""Check if a date is an Italian public holiday"""
|
|
if isinstance(check_date, datetime):
|
|
check_date = check_date.date()
|
|
|
|
year = check_date.year
|
|
month = check_date.month
|
|
day = check_date.day
|
|
|
|
# Check fixed holidays
|
|
if (month, day) in ITALIAN_FIXED_HOLIDAYS:
|
|
return True
|
|
|
|
# Check Easter Monday
|
|
easter_monday = get_easter_monday(year)
|
|
if check_date == easter_monday:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def get_holidays_for_year(year: int) -> list[dict]:
|
|
"""
|
|
Get all holidays for a given year.
|
|
Returns list of {date: YYYY-MM-DD, name: string}
|
|
"""
|
|
holidays = []
|
|
|
|
# Fixed holidays
|
|
holiday_names = [
|
|
"New Year's Day", "Epiphany", "Liberation Day", "Labour Day",
|
|
"Republic Day", "Assumption", "All Saints", "Immaculate Conception",
|
|
"Christmas", "St. Stephen's Day"
|
|
]
|
|
|
|
for (month, day), name in zip(ITALIAN_FIXED_HOLIDAYS, holiday_names):
|
|
holidays.append({
|
|
"date": f"{year}-{month:02d}-{day:02d}",
|
|
"name": name
|
|
})
|
|
|
|
# Easter Monday
|
|
easter_monday = get_easter_monday(year)
|
|
holidays.append({
|
|
"date": easter_monday.strftime("%Y-%m-%d"),
|
|
"name": "Easter Monday"
|
|
})
|
|
|
|
# Sort by date
|
|
holidays.sort(key=lambda h: h["date"])
|
|
return holidays
|
|
|
|
|
|
def is_holiday(check_date: date | datetime | str, region: str = "IT") -> bool:
|
|
"""
|
|
Check if a date is a holiday for the given region.
|
|
Currently only supports IT (Italy).
|
|
"""
|
|
if isinstance(check_date, str):
|
|
check_date = datetime.strptime(check_date, "%Y-%m-%d").date()
|
|
|
|
if region == "IT":
|
|
return is_italian_holiday(check_date)
|
|
|
|
# Default: no holidays
|
|
return False
|