3,627 bytes · the Python implementation · view raw
from typing import List, Optional
#: The weights HMRC applies to the first seven digits, most significant first.#: They are 8 down to 2, which is why the last two digits (the check pair) get#: no weight of their own.
_WEIGHTS = (8, 7, 6, 5, 4, 3, 2)
#: The offset HMRC added for registrations issued from roughly 2010 onwards,#: commonly called the "9755" variant after the constant in their published#: worked example. A number satisfies exactly one of the two checks, never#: both, because 55 is not a multiple of 97.
_MODULUS_9755_OFFSET = 55def _ascii_upper(ch: str) -> str:
"""Uppercase ASCII only. ``str.upper()`` is Unicode-aware: it turns "ß" into "SS" and would make this function disagree with the Rust sibling, which has no such machinery. Nothing in a VAT number is outside ASCII, so restricting the fold keeps all three languages honest. """return chr(ord(ch) - 32) if"a" <= ch <= "z"else ch
def _compact(value: str) -> str:
# Only the ASCII space is ignored. Tabs and non-breaking spaces usually# arrive from a bad paste, and silently accepting them hides that.
out = "".join(_ascii_upper(ch) for ch in value if ch != " ")
return out[2:] if out.startswith("GB") else out
def is_uk_vat_number(value: str) -> bool:
"""Is this a valid UK VAT registration number? Nine digits, or twelve for a branch trader, optionally prefixed "GB" and optionally spaced. The last two of the first nine are a mod-97 check pair. A number that passes is arithmetically well formed. It is not proof that the trader is registered, still registered, or registered for the goods on the invoice: only HMRC's VAT number checker can tell you that, and for a reverse-charge or zero-rated supply you are expected to have asked. """ifnot isinstance(value, str):
returnFalse
compact = _compact(value)
# Nine digits is a standard registration; twelve is a branch trader, where# the final three identify the branch and take no part in the checksum.if len(compact) != 9and len(compact) != 12:
returnFalse
digits: List[int] = []
for ch in compact:
if ch < "0"or ch > "9":
returnFalse
digits.append(ord(ch) - 48)
# 000000000 satisfies the arithmetic and has never been issued to anyone.# Rejecting it costs nothing and stops an all-zero placeholder field from# reading as a valid registration.if all(d == 0for d in digits[:9]):
returnFalse
total = sum(digits[i] * _WEIGHTS[i] for i in range(7))
check = digits[7] * 10 + digits[8]
return _mod97_matches(total, check) or _mod97_matches(total + _MODULUS_9755_OFFSET, check)
def _mod97_matches(total: int, check: int) -> bool:
"""HMRC's "97 check", written the way HMRC writes it: subtract 97 from the weighted total until the result is zero or negative, then compare the magnitude with the check pair. That is the same as ``(-total) % 97`` but this form is what a reviewer can hold against the published guidance. """
remainder = total
while remainder > 0:
remainder -= 97return abs(remainder) == check
def format_uk_vat_number(value: str) -> Optional[str]:
"""The canonical form, "GB999 9999 99", or None if the number does not check out. Branch numbers keep their three-digit suffix. """ifnot is_uk_vat_number(value):
returnNone
compact = _compact(value)
grouped = "GB%s %s %s" % (compact[0:3], compact[3:7], compact[7:9])
return grouped + " " + compact[9:] if len(compact) == 12else grouped