Files
F-r-ldraledigplannerare/backend/app/utils/dates.py
T
2026-06-01 20:30:17 +02:00

65 lines
1.7 KiB
Python

"""Date calculation utilities for leave planning.
Pure functions with no I/O or state. Used by planning and analytics services.
"""
import datetime
def add_months(date: datetime.date, n: int) -> datetime.date:
"""Add or subtract months from a date, handling month-end edge cases.
Examples:
add_months(datetime.date(2026, 1, 31), 1) -> 2026-02-28 (or 29 in leap year)
add_months(datetime.date(2026, 3, 31), -1) -> 2026-02-28
"""
month = date.month + n
year = date.year
while month > 12:
month -= 12
year += 1
while month < 1:
month += 12
year -= 1
# Handle month-end: if original day doesn't exist in target month,
# use last day of that month
day = min(date.day, _days_in_month(year, month))
return datetime.date(year, month, day)
def _days_in_month(year: int, month: int) -> int:
"""Return number of days in the given month."""
if month == 2:
return 29 if _is_leap_year(year) else 28
if month in (4, 6, 9, 11):
return 30
return 31
def _is_leap_year(year: int) -> bool:
"""Check if a year is a leap year."""
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
def age_in_months(birth_date: datetime.date, on: datetime.date) -> int:
"""Calculate exact age in months between two dates.
If on < birth_date, returns negative value (person not yet born).
"""
months = (on.year - birth_date.year) * 12 + (on.month - birth_date.month)
# Adjust if birthday hasn't occurred this month yet
if on.day < birth_date.day:
months -= 1
return months
def fourth_birthday(birth_date: datetime.date) -> datetime.date:
"""Return the date when a child turns 4 years old."""
return add_months(birth_date, 48)