cadrumo.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 governed event facts in the validated authority 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 resolves only governed, BOE-cited event facts from the validated registry authority.

class CalendarCCAA(value, *, _registry_validated=False)[source]

Bases: str

Registry-projected ISO 3166-2:ES deadline-calendar territory token.

Fact 0143 owns the nineteen territory codes used by the deadline calendar, including the foral communities and autonomous cities that are intentionally outside fact 0129’s fifteen-member tax-residence vocabulary. Direct token construction is reserved for the typed catalogue projection; callers resolve membership through the same operation before constructing one.

Parameters:
Return type:

Self

classmethod from_registry(value)[source]

Construct the typed value from its canonical registry token.

Return type:

Self

Parameters:

value (str)

property value: str

Return the canonical ISO code.

property name: str

Return the canonical ISO code for diagnostics.

class HolidayJurisdiction(*values)[source]

Bases: StrEnum

Layer 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 LOCAL value would be out of scope.

NATIONAL
CCAA
class Holiday(**data)[source]

Bases: BaseModel

A single declared holiday.

Parameters:

data (Any)

holiday_date: date
jurisdiction: HolidayJurisdiction
ccaa_code: CalendarCCAA | None
name: _NonEmptyShortString
class HolidayCalendar(**data)[source]

Bases: BaseModel

A year’s BOE-published holiday calendar.

boe_ref is the citation stem of the BOE Resolución that published the annual relación de fiestas laborales. boe_url is an optional convenience anchor for the same resolution.

Parameters:

data (Any)

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: BaseModel

Outcome 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.

shifted is the boolean predicate “did the close date move?”; shift_days is the (always non-negative) day count between original and adjusted; shift_reason is a stable identifier consumers may use for output-formatting and rule-explanation; the optional holiday_refs and jurisdictions tuples carry the specific holidays whose presence on the calendar caused the shift.

Parameters:

data (Any)

original_close_date: date
adjusted_close_date: date
shifted: bool
shift_days: NonNegativeInt
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, *, operation)[source]

Load a calendar through the caller’s pinned operation.

Return type:

HolidayCalendar

Parameters:
holiday_calendar_from_authority(year, *, operation)[source]

Resolve one complete published calendar from the governed-fact authority.

The publication event is resolved first. It is the positive proof that an absent per-date holiday event means an ordinary business day rather than an unpublished calendar year. All individual holiday values are then read through exact event queries, retaining the authority’s provenance.

Return type:

HolidayCalendar

Parameters:
is_business_day(candidate, *, calendar, ccaa_code)[source]

Return True when candidate is 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_code is supplied) not a CCAA holiday for that ccaa-year pair. When ccaa_code is None the predicate degrades to national-only: callers with no tax-residence information get weekend + national detection but miss CCAA shifts.

Return type:

bool

Parameters:
next_business_day(start, *, calendar, ccaa_code)[source]

Return the first date on or after start that 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:

date

Parameters:
shift_deadline(original_close_date, *, modelo, ccaa_code, calendar=None, operation)[source]

Apply the AEAT deadline-shift rule and return a DeadlineShift result.

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 DeadlineShift with reason modelo_exception.

When calendar is omitted, the caller’s pinned operation resolves a BOE-published calendar through the governed publication and holiday event facts. A missing publication fact fails closed; it is never treated as a holiday-free calendar. Callers that already hold a calendar may pass it directly while retaining the operation that owns the workflow.

Return type:

DeadlineShift

Parameters: