aeat.domain.deadlines._festivos module¶
Spanish business-day calendar and AEAT deadline shift.
This module is the holiday-adjustment service. It loads BOE-
published national plus autonomous-community (“CCAA”) holiday
calendars from the project’s
registry/aeat/calendars/ directory and exposes pure functions that
answer two questions:
Is a given date a día hábil (business day) for AEAT filings in the taxpayer’s CCAA of tax residence?
Given an AEAT-registered close date, what is the legally-due filing deadline once weekends, national holidays, and CCAA holidays are considered, and what was the reason for the shift?
AEAT’s published deadline-shift rule (Calendario del Contribuyente, “Vencimientos en días inhábiles, sábados o festivos”) is:
“Si el último día del plazo coincide con un sábado, domingo o festivo, el plazo se entiende ampliado hasta el primer día hábil siguiente.”
The rule covers national holidays plus the autonomous-community holiday of the taxpayer’s domicilio fiscal. Local (municipal) holidays do NOT affect AEAT filing deadlines and are not part of this calendar.
One well-known exception: Modelo 369 (OSS / IOSS one-stop-shop)
deadlines do NOT shift, even when the close date falls on a non-
business day, because the OSS / IOSS regime is governed by the EU
Council Directive’s harmonised cutoffs and the AEAT cannot lengthen
the EU-wide window unilaterally. The exception list is encoded in
MODELOS_WITHOUT_SHIFT so future modelo additions land as data,
not as a fork in shift_deadline().
The substrate is pure domain logic: it never touches the CLI, never mutates input, and never reaches outside the project for live calendar data (calendars are git-tracked, BOE-cited TOML).
- class CalendarCCAA(*values)[source]¶
Bases:
StrEnumSpanish autonomous communities and the two autonomous cities, keyed by ISO 3166-2:ES code.
AEAT filing deadlines may shift when the close date coincides with a holiday in the taxpayer’s CCAA of tax residence (domicilio fiscal). The two autonomous cities of Ceuta and Melilla each publish their own holiday calendar and behave like a CCAA for this purpose.
The codes match the ISO 3166-2:ES standard and the BOE-published holiday resolutions.
- ANDALUCIA¶
- ARAGON¶
- ASTURIAS¶
- ILLES_BALEARS¶
- CANARIAS¶
- CANTABRIA¶
- CASTILLA_LA_MANCHA¶
- CASTILLA_Y_LEON¶
- CATALUNA¶
- EXTREMADURA¶
- GALICIA¶
- LA_RIOJA¶
- MADRID¶
- MURCIA¶
- NAVARRA¶
- PAIS_VASCO¶
- VALENCIA¶
- CEUTA¶
- MELILLA¶
- class HolidayJurisdiction(*values)[source]¶
Bases:
StrEnumLayer of government that declared the holiday.
NATIONAL— declared by the State; observed everywhere.CCAA— declared by an autonomous community; observed only in that CCAA’s territory.
AEAT does not consider municipal-level holidays for filing-deadline shifts, so a corresponding
LOCALvalue would be out of scope.- NATIONAL¶
- CCAA¶
- class Holiday(**data)[source]¶
Bases:
BaseModelA single declared holiday.
- Parameters:
holiday_date (date)
jurisdiction (HolidayJurisdiction)
ccaa_code (CalendarCCAA | None)
name (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=None, min_length=1, max_length=200, pattern=None, ascii_only=None)])
- holiday_date: date¶
- jurisdiction: HolidayJurisdiction¶
- ccaa_code: CalendarCCAA | None¶
- name: _NonEmptyShortString¶
- class HolidayCalendar(**data)[source]¶
Bases:
BaseModelA year’s BOE-published holiday calendar.
boe_refis the citation stem of the BOE Resolución that published the annual relación de fiestas laborales.boe_urlis an optional convenience anchor for the same resolution.- Parameters:
- year: Annotated[int, Field(ge=2000, le=2100)]¶
- boe_ref: _NonEmptyShortString¶
- boe_url: str | None¶
- national: tuple[Holiday, ...]¶
- ccaa: tuple[Holiday, ...]¶
- class DeadlineShift(**data)[source]¶
Bases:
BaseModelOutcome of applying the AEAT deadline-shift rule to one close date.
Carries the original AEAT-registered close date, the legally-adjusted close date after weekend / holiday shifts, a structured reason, and references to the holiday source(s) that drove the shift.
shiftedis the boolean predicate “did the close date move?”;shift_daysis the (always non-negative) day count between original and adjusted;shift_reasonis a stable identifier consumers may use for output-formatting and rule-explanation; the optionalholiday_refsandjurisdictionstuples carry the specific holidays whose presence on the calendar caused the shift.- Parameters:
original_close_date (date)
adjusted_close_date (date)
shifted (bool)
shift_days (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=0)])])
shift_reason (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=None, min_length=1, max_length=200, pattern=None, ascii_only=None)])
jurisdictions (tuple[HolidayJurisdiction, ...])
- original_close_date: date¶
- adjusted_close_date: date¶
- shifted: bool¶
- shift_days: Annotated[int, Field(ge=0)]¶
- shift_reason: _NonEmptyShortString¶
- jurisdictions: tuple[HolidayJurisdiction, ...]¶
- holiday_refs: tuple[str, ...]¶
- MODELOS_WITHOUT_SHIFT: tuple[str, ...]¶
Modelos whose deadlines are NOT shifted by the AEAT día-inhábil rule.
Per the AEAT Calendario del Contribuyente, the OSS / IOSS one-stop-shop regime (Modelo 369) is governed by the EU-harmonised cutoff date and AEAT cannot extend the window unilaterally even when the close date falls on a Spanish holiday or weekend. Adding new modelos to this tuple keeps the exception list data-driven;
shift_deadline()never grows a switch statement.
- load_holiday_calendar(year)[source]¶
Load the BOE-published holiday calendar for
year.Reads
registry/aeat/calendars/festivos-{year}.toml, parses it into immutableHolidayrecords under theHolidayCalendaraggregate, and caches the result. The cache is unbounded by use but bounded by call site (lru_cache withmaxsize=64).Raises
DeadlineValidationErrorwhen the TOML file is missing or malformed (caller-recoverable; the surrounding deadline engine can degrade to weekend-only shifts).- Return type:
- Parameters:
year (int)
- is_business_day(candidate, *, calendar, ccaa_code)[source]¶
Return True when
candidateis a business day for AEAT filings.A date is a business day when it is not a Saturday, not a Sunday, not a national holiday for that year, and (when
ccaa_codeis supplied) not a CCAA holiday for that ccaa-year pair. Whenccaa_codeisNonethe predicate degrades to national-only: callers with no tax-residence information get weekend + national detection but miss CCAA shifts.- Return type:
- Parameters:
candidate (date)
calendar (HolidayCalendar)
ccaa_code (CalendarCCAA | None)
- next_business_day(start, *, calendar, ccaa_code)[source]¶
Return the first date on or after
startthat is a business day.The walk is bounded by 14 days (a fortnight) to keep the function safe against pathological inputs; in practice the AEAT calendar never strings more than four non-business days together (e.g. Semana Santa weekend + Jueves Santo + Viernes Santo + Lunes de Pascua = 4 days).
- Return type:
- Parameters:
start (date)
calendar (HolidayCalendar)
ccaa_code (CalendarCCAA | None)
- shift_deadline(original_close_date, *, modelo, ccaa_code, calendar=None)[source]¶
Apply the AEAT deadline-shift rule and return a
DeadlineShiftresult.The rule moves the deadline to the next business day when the original close date is a Saturday, Sunday, national holiday, or CCAA holiday of the taxpayer’s tax residence.
Modelo-specific exceptions (e.g., Modelo 369 OSS / IOSS) bypass the shift and return an unshifted
DeadlineShiftwith reasonmodelo_exception.When
calendaris omitted the function loads it viaload_holiday_calendar()keyed by the original close date’s year. Callers that already hold the calendar may pass it directly to avoid a second lookup.- Return type:
- Parameters:
original_close_date (date)
modelo (str)
ccaa_code (CalendarCCAA | None)
calendar (HolidayCalendar | None)