Skip to main content

duty27-eu-vat

The standard VAT rate in every EU member state, on any date since 1 January 2016. No dependencies, works offline, fully typed. Python 3.9 and later.

from duty27_eu_vat import get_standard_rate

get_standard_rate("DE")                 # today's rate: Decimal('19')
get_standard_rate("DE", "2020-07-01")   # Decimal('16'), Germany's temporary cut
get_standard_rate("FI", "2024-09-01")   # Decimal('25.5')

Rates change, sometimes for a few months only, and a hardcoded table silently goes wrong. This package keeps the dated history in one place so "what was the rate on the day of this sale?" has a real answer. Rates are exact Decimal values, not floats, so price * rate / 100 has no rounding noise.

Install

pip install duty27-eu-vat

Use

from datetime import date
from duty27_eu_vat import (
    get_standard_rate, get_rate_history, get_rate_changes, list_countries, format_rate, DATA_AS_OF,
)

get_standard_rate("GR", date(2016, 6, 1))   # Decimal('24'); GR is accepted for Greece (the EU code is EL)

get_rate_history("DE")
# [RateWindow(effective_from=datetime.date(2016, 1, 1), rate=Decimal('19')),
#  RateWindow(effective_from=datetime.date(2020, 7, 1), rate=Decimal('16')),
#  RateWindow(effective_from=datetime.date(2021, 1, 1), rate=Decimal('19'))]

get_rate_changes(since="2025-01-01")        # RateChange(country='RO', date=date(2025, 8, 1), from_rate=19, to_rate=21), ...
list_countries()                            # the 27 member states: Country(code='AT', name='Austria'), ...
format_rate(Decimal("25.5"), "de")          # '25,5 %'
DATA_AS_OF                                  # the date the data was last checked
Function Returns
get_standard_rate(country, on=None) The rate in force on a date (date, datetime, or "YYYY-MM-DD"). Today (UTC) if omitted. A date after the last known change returns the latest known rate.
get_rate_history(country) Every rate the country has had since 2016, in order.
get_rate_changes(country=None, since=None) Every change, newest first.
list_countries() The 27 member states, with EU codes (Greece is EL; GR is accepted) and English names.
format_rate(rate, locale="en") The rate as a localised percentage. Supports en, de, fr, es.
normalize_country(code) The EU code for a country code, case-insensitive.

Errors are never guesses. An unknown or non-EU country raises UnknownCountryError; a date before 2016-01-01 raises DateOutOfRangeError; a malformed date string raises ValueError; a date of the wrong type raises TypeError. (UnknownCountryError and DateOutOfRangeError are both ValueErrors.) An aware datetime is converted to UTC first; a naive one is read as its own calendar day.

What it covers, and what it does not

  • Standard rates only. Reduced rates (for example e-books, newspapers and periodicals) are not included.
  • Lookup, not calculation. It returns the rate. It does not work out net or gross amounts, rounding, whether a sale is reverse-charged, or OSS reports: those are in the Duty27 API (see below).
  • 27 EU member states, from 2016-01-01.
  • The data has an age. DATA_AS_OF is the date it was last compared with the rates Duty27 publishes. A rate that changed after that date will not be here until a new version is released. For anything that must be right today, check the source, or use the API below.
  • The rates are compiled from the European Commission's TEDB service and cross-checked against the Commission's own historical rate tables and national sources. Every change since 2016 cites the law or tax-authority notice that made it, and the 2016 starting rates cite the Commission's rate table. The citations are not bundled in this package: they are listed at https://duty27.com/vat-rates/history, and the CSV and JSON there carry them for every row.

This package provides standard VAT rate data for information. It is not tax advice.

Need more than a rate?

Knowing the rate is the easy part. Deciding what to charge, and keeping the proof, is the rest: whether a sale is B2B and reverse-charged, whether the EU €10,000 threshold has been passed, whether a VAT number is valid, and an archive and OSS report for your filings. That is what the Duty27 API does, and it is free to try (500 calculations a month, no credit card).

Working on it

This folder is self-contained: PYTHONPATH=src python -m unittest discover -s tests runs the tests with nothing to install. The rate data in src/duty27_eu_vat/_data.py and the test file tests/test-vectors.csv are generated from the shared source at the repository root (./build.sh data), not edited by hand.

Licence

  • Code: MIT.
  • Bundled rate data: CC BY 4.0. Please credit it as Rate data: Duty27 (https://duty27.com/vat-rates/history), CC BY 4.0. The package exports this text as ATTRIBUTION.

Metadata

Release files for duty27-eu-vat 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for duty27-eu-vat 0.1.0
File Size Uploaded
duty27_eu_vat-0.1.0.tar.gz 9.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for duty27-eu-vat 0.1.0
File Interpreter ABI Platform
duty27_eu_vat-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 20.4 kB

Release files / duty27_eu_vat-0.1.0.tar.gz

Download URL duty27_eu_vat-0.1.0.tar.gz
Size 9.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0179ecaecae98d88ec20be9e1bc0db3fe8f907d36f889f8e6cd211e1fafd07cd
BLAKE2b-256 checksum
How to use checksums
ae895cd10983dd15cf00c85214770ad9b2865569c462a1e19e1578a586c65be7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release files / duty27_eu_vat-0.1.0-py3-none-any.whl

Download URL duty27_eu_vat-0.1.0-py3-none-any.whl
Size 11.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c340985610c04695d39b666086bb1feeaf5fde369e19ab0a12d64e6ee82cb9e2
BLAKE2b-256 checksum
How to use checksums
eb04910efe84bd0fb1cdbb13dda580b687b889649a02df86500dc70477779723
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page