diff --git a/stdnum/se/bankgiro.py b/stdnum/se/bankgiro.py new file mode 100644 index 00000000..5703520d --- /dev/null +++ b/stdnum/se/bankgiro.py @@ -0,0 +1,88 @@ +# bankgiro.py - functions for handling Swedish Bankgiro numbers +# coding: utf-8 +# +# Copyright (C) 2026 Gracestack (Kim Cedendahl) +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, see . + +"""Bankgiro (Swedish bank giro number). + +The Bankgiro number is used in Sweden to identify bank giro accounts +for invoice payments and other bank transfers. The number consists of +7 or 8 digits and includes a check digit calculated using the Luhn +(mod-10) algorithm, which is the same as used by the related PlusGiro +numbers. + +Bankgiro numbers are commonly formatted as NNN-NNNN (7 digits) or +NNNN-NNNN (8 digits) with a hyphen separating the groups. + +More information: + +* https://www.bankgirot.se/en/ + +>>> validate('9000001') +'9000001' +>>> validate('9000002') # invalid check digit +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> validate('123456789') # too long +Traceback (most recent call last): + ... +InvalidLength: ... +>>> format('9000001') +'900-0001' +>>> format('50501501') +'5050-1501' +""" + +from __future__ import annotations + +from stdnum import luhn +from stdnum.exceptions import * +from stdnum.util import clean, isdigits + + +def compact(number: str) -> str: + """Convert the number to the minimal representation. This strips the + number of any valid separators and removes surrounding whitespace.""" + return clean(number, ' -.').strip() + + +def validate(number: str) -> str: + """Check if the number is a valid Bankgiro number. This checks + the length, formatting and check digit.""" + number = compact(number) + if not isdigits(number): + raise InvalidFormat() + if len(number) not in (7, 8): + raise InvalidLength() + return luhn.validate(number) + + +def is_valid(number: str) -> bool: + """Check if the number is a valid Bankgiro number.""" + try: + return bool(validate(number)) + except ValidationError: + return False + + +def format(number: str) -> str: + """Reformat the number to the standard presentation format.""" + number = compact(number) + if len(number) == 7: + return number[:3] + '-' + number[3:] + else: + return number[:4] + '-' + number[4:] \ No newline at end of file diff --git a/tests/test_se_bankgiro.doctest b/tests/test_se_bankgiro.doctest new file mode 100644 index 00000000..d22a2eda --- /dev/null +++ b/tests/test_se_bankgiro.doctest @@ -0,0 +1,100 @@ +test_se_bankgiro.doctest - more detailed doctests for stdnum.se.bankgiro module + +Copyright (C) 2026 Gracestack (Kim Cedendahl) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see . + + +This file contains more detailed doctests for the stdnum.se.bankgiro +module. It tries to cover more corner cases and detailed functionality. + +>>> from stdnum.se import bankgiro + + +Test valid 7-digit numbers. + +>>> bankgiro.validate('9000001') +'9000001' +>>> bankgiro.validate('900-0001') +'9000001' + + +Test valid 8-digit numbers. + +>>> bankgiro.validate('50501501') +'50501501' +>>> bankgiro.validate('5050-1501') +'50501501' + + +Test formatting variants. + +>>> bankgiro.format('9000001') +'900-0001' +>>> bankgiro.format('50501501') +'5050-1501' +>>> bankgiro.format('900-0001') +'900-0001' +>>> bankgiro.format('5050-1501') +'5050-1501' + + +Test compact. + +>>> bankgiro.compact('900-0001') +'9000001' +>>> bankgiro.compact(' 5050 1501 ') +'50501501' + + +Test invalid numbers (wrong check digit). + +>>> bankgiro.validate('9000002') +Traceback (most recent call last): + ... +InvalidChecksum: ... + + +Test invalid length. + +>>> bankgiro.validate('12345') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> bankgiro.validate('123456789') +Traceback (most recent call last): + ... +InvalidLength: ... + + +Test invalid format (non-numeric). + +>>> bankgiro.validate('abcdefg') +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> bankgiro.validate('') +Traceback (most recent call last): + ... +InvalidFormat: ... + + +Test is_valid convenience function. + +>>> bankgiro.is_valid('9000001') +True +>>> bankgiro.is_valid('9000002') +False +>>> bankgiro.is_valid('') +False \ No newline at end of file