Skip to content

Repository files navigation

calendario-fiscal-es

When every Spanish tax form is due, computed in Python with zero dependencies.

tests Python 3.9+ License: MIT Dependencies

🇬🇧 English · 🇪🇸 Versión en español

calendario-fiscal-es

If your software invoices, does bookkeeping or sends reminders in Spain, sooner or later somebody hardcodes that modelo 303 is due "on the 20th". Then a 20th falls on a Saturday. Then Q4 turns out to be due on the 30th, not the 20th. Then a client pays by direct debit and has to file five days earlier. This library computes those dates.

from datetime import date
from calendario_fiscal_es import proximos

for p in proximos(date(2026, 8, 30), dias=45):
    print(p.fin, f"modelo {p.modelo}", p.periodo, f"({p.dias_restantes(date(2026, 8, 30))} days)")

Install

pip install git+https://github.com/mindset-code/calendario-fiscal-es

Python 3.9+. Zero dependencies — only datetime, enum and dataclasses from the standard library.

Verify the install without writing code:

calendario-fiscal-es autocomprobar

One deadline

from calendario_fiscal_es import Modelo, Periodo, plazo

p = plazo(Modelo.M303, ejercicio=2026, periodo=Periodo.T1)

p.inicio             # date(2026, 4, 1)
p.fin                # the real due date, already shifted to a business day
p.fin_nominal        # date(2026, 4, 20) — before shifting
p.fin_domiciliacion  # date(2026, 4, 15) — five calendar days earlier
p.trasladado         # True if the 20th fell on a weekend or a holiday
p.dias_restantes(date(2026, 4, 2))
p.vigente(date(2026, 4, 10))

ejercicio is the period being declared, not the year you file it. Q4 of 2026 is filed in January 2027, but its ejercicio is 2026:

plazo(Modelo.M303, 2026, Periodo.T4).fin_nominal   # date(2027, 1, 30)

From the command line

$ calendario-fiscal-es proximos --dias 15
$ calendario-fiscal-es plazo 303 2026 --periodo 4T
$ calendario-fiscal-es --json ejercicio 2026 --modelo 303

Everything the library computes is reachable without writing Python, and --json makes it composable. Full reference: docs/cli.md.

The traps it handles

Each one is covered by a test:

  • Q4 does not close on the same day for everyone. 111 and 115 are due on 20 January; 303 and 130, on the 30th. The most repeated confusion in a firm's spreadsheet.
  • A due date on a weekend or holiday moves to the next business day (article 30.5, Ley 39/2015). Good Friday included, computed with the Meeus/Jones/Butcher algorithm.
  • Direct debit shortens the deadline by five calendar days, and only for self-assessments. An informational return like 347 or 390 debits nothing, so its fin_domiciliacion is None instead of a fake date.
  • The Personal Income Tax campaign is never extrapolated. Its dates are set each year by a ministerial order. Asking for an unverified year raises AnioNoVerificado instead of returning a confident-looking invention.

Coverage

Cadence Models
Quarterly, closes on the 20th 111, 115, 123
Quarterly, Q4 until the 30th 303, 130, 131, 349
January annual summaries 390, 190, 180, 184, 193
Annual, other months 347 (February), 720 and 721 (March), 232 (November)
Corporate Income Tax 200 (July), 202 (April, October, December)
Campaign 100 — Personal Income Tax, verified years only

A whole year at once

from calendario_fiscal_es import plazos_del_ejercicio

for p in plazos_del_ejercicio(2026):
    print(p)

And to warn about your own forms only:

proximos(date.today(), dias=30, modelos=["303", "111"])

Regional and local holidays

The library knows the ten national holidays: nine fixed dates plus Good Friday. It does not know your region's or your town's, because those are set every year and in every place. Inject them:

plazo(Modelo.M303, 2026, Periodo.T1, festivos_extra={date(2026, 9, 11)})

Asking you is better than handing back a wrong date that looks right.

Documentation

Page What is in it
Getting started install, first deadline, the ejercicio trap
Deadlines how a rule becomes a date; the Q4 divergence
Business days article 30.5, the ten holidays, Good Friday
Command line every subcommand with real output
Integration cron reminders, an .ics feed, caching
FAQ the questions that come back

A runnable end-to-end example lives in examples/agenda_fiscal.py: a full year's agenda, a reminder pass, regional holidays and an iCalendar export. The test suite runs it, so it cannot rot.

Scope

This computes dates. It does not fill in forms, compute amounts, or file anything with the Spanish Tax Agency. For the chained hash VERI*FACTU requires of billing systems, see pyverifactu-huella.

This is not tax advice. The deadlines are those of the taxpayer calendar in force at the time of writing; check the official source before resting a real obligation on them, and check each year whether the Personal Income Tax campaign or any deadline has moved.

Sources

  • Calendario del contribuyente — Spanish Tax Agency
  • Ley 39/2015, article 30.5 — extension to the next business day
  • Real Decreto Legislativo 2/2015 (Workers' Statute), article 37.2 — the four national holidays reserved by law
  • The labour calendar published in the BOE each year — the remaining five national holidays

Contributing

Bugs and proposals go to issues. If you send a change to a date, bring the link to the official source and a test: a wrong date here costs somebody a surcharge. See CONTRIBUTING.md.

git clone https://github.com/mindset-code/calendario-fiscal-es
cd calendario-fiscal-es
pip install -e ".[dev]"
pytest -v

License

MIT — see LICENSE.


Maintained by Mindset & Code. If you need to automate deadline reminders, reconciliation or invoicing inside a system that is already running, write to contacto@mindset-code.com.

About

Plazos de presentación de los modelos tributarios españoles (AEAT) en Python puro, sin dependencias: traslado a día hábil y fecha límite de domiciliación.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages