Delete date helpers nothing calls
A hand-rolled month-arithmetic module survived the move to dateutil with no caller left but its own tests, and two predicates alongside it were never wired up at all. An error import was also being deferred inside a function for a circular dependency that does not exist.
This commit is contained in:
@@ -1,64 +0,0 @@
|
||||
"""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)
|
||||
@@ -183,12 +183,6 @@ def is_swedish_holiday(date: datetime.date) -> bool:
|
||||
return date in holidays_for_year
|
||||
|
||||
|
||||
def get_holiday_name(date: datetime.date) -> str | None:
|
||||
"""Get the name of a Swedish public holiday, or None if not a holiday."""
|
||||
holidays_for_year = get_swedish_holidays(date.year)
|
||||
return holidays_for_year.get(date)
|
||||
|
||||
|
||||
def is_non_working_day(date: datetime.date) -> bool:
|
||||
"""True if `date` is a weekend day, a public holiday, or a de facto day
|
||||
off (julafton, midsommarafton, nyårsafton, ...).
|
||||
|
||||
@@ -13,7 +13,7 @@ from datetime import date, timedelta
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from app.config import settings
|
||||
from app.config import LeaveRules, settings
|
||||
from app.entities.child import Child
|
||||
from app.entities.personal_data import PersonalData
|
||||
from app.entities.planned_leave import PlannedLeaveDay
|
||||
@@ -32,6 +32,19 @@ def weekly_intensity(planned_days: list[PlannedLeaveDay], period_weeks: float) -
|
||||
return len(fp_days) / period_weeks
|
||||
|
||||
|
||||
def default_tier_pool_share(rules: LeaveRules) -> tuple[int, int]:
|
||||
"""The per-parent (sjukpenning_left, lagsta_left) assumed when no FK
|
||||
snapshot exists yet.
|
||||
|
||||
`rules.sjukpenning_days`/`lagsta_niva_days` are the WHOLE CHILD's combined
|
||||
tier totals (390 + 90), split across both parents — halving them gives a
|
||||
single parent's default share. Shared by `AllocationInputLoader.load` and
|
||||
`PlanningService._tier_pool_left` so they can never derive it differently
|
||||
(see "Bug A": they once did, 390/90 in one place vs. 195/45 in the other).
|
||||
"""
|
||||
return rules.sjukpenning_days // 2, rules.lagsta_niva_days // 2
|
||||
|
||||
|
||||
def resolve_age4_savable_cap(planning_mode: str, parent_count: int) -> int:
|
||||
"""Single source of truth for which age-4 savable-days ceiling applies.
|
||||
|
||||
@@ -100,11 +113,6 @@ def age4_pooling_loss(
|
||||
return max(reachable_pooled - reachable_per_parent, 0)
|
||||
|
||||
|
||||
def is_child_under_age(birth_date: date, months: int) -> bool:
|
||||
"""True if the child's age in months is less than `months`."""
|
||||
return date.today() < birth_date + relativedelta(months=months)
|
||||
|
||||
|
||||
def is_child_over_age(birth_date: date, years: int) -> bool:
|
||||
"""True if the child has turned `years`."""
|
||||
return date.today() >= birth_date + relativedelta(years=years)
|
||||
|
||||
Reference in New Issue
Block a user