Initial commit: Parking Manager
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
This commit is contained in:
116
services/holidays.py
Normal file
116
services/holidays.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user