Validating an International Bank Account Number

SRIV Sept 19,2010 I had a need to validate IBAN account numbers, so following the example on Wikipedia I wrote a script to do the task. Feel free to test against your bank account numbers and include the country test in the switch with a note saying it works for you.

#!/usr/bin/env tclsh

# Steve Redler IV  SR Technology Sept 19, 2010
# Validating an International Bank Account Number
# as per http://en.wikipedia.org/wiki/International_Bank_Account_Number

set iban "GB82 WEST 1234 5698 7654 32"
set iban0 [string map {" " ""} [string toupper $iban]]
puts "iban0=$iban0"
switch -- [string range $iban0 0 1] {
  HU {if {[string length $iban0] != 28} {
               puts "IBAN must be 28 characters for Hungary"
               exit 0}        
            }
  DE {if {[string length $iban0] != 22} {
               puts "IBAN must be 22 characters for Germany"
               exit 0}        
            }
  IS {if {[string length $iban0] != 26} {
               puts "IBAN must be 26 characters for Iceland"
               exit 0}        
            } 
  GB {if {[string length $iban0] != 22} {
               puts "IBAN must be 22 characters for United Kingdom"
               exit 0}        
            } 
  PL {if {[string length $iban0] != 28} {
               puts "IBAN must be 28 characters for POLAND"
               exit 0}        
            }                                     
}

set iban1 "[string range $iban0 4 end][string range $iban0 0 3]"
puts "iban1=$iban1"

set iban2 ""
foreach char [split $iban1 ""] {
        set ascii [scan $char %c]
        if {$ascii >= 65} {
                append iban2 [expr $ascii -55]
        } else {
          append iban2 $char        
        }
        puts "ascii $ascii  char $char  $iban2"        
}

set iban2 [string trimleft $iban2 0]
puts "iban2=$iban2"

set modulus [expr $iban2 % 97]
puts "modulus=$modulus"

if {$modulus == 1} {
  puts "IBAN number is valid"        
} else {
  puts "IBAN number is invalid"        
}

MaxJarek: In tcllib we have valtype::iban for IBAN validation.

package require valtype::iban
try {
  valtype::iban validate GB82WEST12345698765432
} on error {result option} {
  puts $result
}

HolgerJ provides a snit-free proc for validating IBANs. 2023-07-27

####################################################################
## Validation of IBAN numbers.
#
# References:
#    http://en.wikipedia.org/wiki/International_Bank_Account_Number
#    https://www.iban.com/structure
#
#
# File name: iban-0.1.tm
####################################################################

namespace eval ::iban {

  # Not all countries are already in the SEPA system. Nevertheless, they may use IBANs.
  array set ibanLengths {
    AL 28 AD 24 AT 20 AZ 28 BH 22 BY 28 BE 16 BA 20 BR 29 BG 22 BI 27 CR 22 HR 21 CY 28 CZ 24 DK 18
    DJ 27 DO 28 EG 29 SV 28 EE 20 FO 18 FI 18 FR 27 GE 22 DE 22 GI 23 GR 27 GL 18 GT 28 VA 22 HU 28
    IS 26 IQ 23 IE 22 IL 23 IT 27 JO 30 KZ 20 XK 20 KW 30 LV 21 LB 28 LY 25 LI 21 LT 20 LU 20 MT 31
    MR 27 MU 30 MD 24 MC 27 MN 20 ME 22 NL 18 NI 28 MK 19 NO 15 PK 24 PS 29 PL 28 PT 25 QA 29 RO 24
    RU 33 LC 32 SM 27 ST 25 SA 24 RS 22 SC 31 SK 24 SI 19 SO 23 ES 24 SD 18 SE 24 CH 21 TL 23 TN 24
    TR 26 UA 29 AE 23 GB 22 VG 24
  } ;# array

  set charValues {
    A 10 B 11 C 12 D 13 E 14 F 15 G 16 H 17 I 18 J 19 K 20 L 21 M 22
    N 23 O 24 P 25 Q 26 R 27 S 28 T 29 U 30 V 31 W 32 X 33 Y 34 Z 35
  } ;# list


  proc validate {iban} {
    # Validate an International Bank Account Number
    #   iban - IBAN to be validated
    # Returns the IBAN with capitalised characters if the check digits are correct. Throws an error with 
    # an error message if the IBAN's check digits are incorrect, the length of the IBAN is incorrect
    # or the first two characters don't specify a valid country code.
    
    variable ibanLengths
    variable charValues 
    
    set iban [string toupper $iban]

    if {![regexp {^[A-Z]{2}[0-9A-Z]+$} $iban]} {
      return -code error "IBAN number, expected two-letter country code followed by alphanumerics"
    } ;# if

    set country [string range $iban 0 1]

    if {![info exists ibanLengths($country)]} {
      return -code error "IBAN number, unknown country code '$country'"
    } ;# if

    set expectedLength $ibanLengths($country)
    set actualLength [string length $iban]
    if {$actualLength != $expectedLength} {
      return -code error "IBAN number of country '$country' is expected to be of length $expectedLength, not $actualLength."
    } ;# if

    set number [string range $iban 4 end][string range $iban 0 3]
    set number [string map $charValues $number]
    set number [string trimleft $number 0]

    if {($number % 97) != 1} {
      return -code error "IBAN number '$iban', check digits don't match."
    } ;# if

    return $iban
  } ;# proc validate

} ;# namespace

package provide ::iban 0.1


proc test {} {
  # Test proc with an example IBAN of every country where IBANs are defined.
  
  set ibans { 
AL35202111090000000001234567
AD1400080001001234567890
AT483200000012345864
AZ77VTBA00000000001234567890
BH02CITI00001077181611
BY86AKBB10100000002966000000
BE71096123456769
BA393385804800211234
BR1500000000000010932840814P2
BG18RZBB91550123456789
BI1320001100010000123456789
CR23015108410026012345
HR1723600001101234565
CY21002001950000357001234567
CZ5508000000001234567899
DK9520000123456789
DJ2110002010010409943020008
DO22ACAU00000000000123456789
EG800002000156789012345180002
SV43ACAT00000000000000123123
EE471000001020145685
FO9264600123456789
FI1410093000123458
FR7630006000011234567890189
GE60NB0000000123456789
DE75512108001245126199
GI56XAPO000001234567890
GR9608100010000001234567890
GL8964710123456789
GT20AGRO00000000001234567890
VA59001123000012345678
HU93116000060000000012345676
IS750001121234563108962099
IQ20CBIQ861800101010500
IE64IRCE92050112345678
IL170108000000012612345
IT60X0542811101000000123456
JO71CBJO0000000000001234567890
KZ244350000012344567
XK051212012345678906
KW81CBKU0000000000001234560101
LV97HABA0012345678910
LB92000700000000123123456123
LY38021001000000123456789
LI7408806123456789012
LT601010012345678901
LU120010001234567891
MT31MALT01100000000000000000123
MR1300020001010000123456753
MU43BOMM0101123456789101000MUR
MD21EX000000000001234567
MC5810096180790123456789085
MN580050099123456789
ME25505000012345678951
NL02ABNA0123456789
NI79BAMC00000000000003123123
MK07200002785123453
NO8330001234567
PK36SCBL0000001123456702
PS92PALS000000000400123456702
PL10105000997603123456789123
PT50002700000001234567833
QA54QNBA000000000000693123456
RO66BACX0000001234567890
RU0204452560040702810412345678901
LC14BOSL123456789012345678901234
SM76P0854009812123456789123
ST23000200000289355710148
SA4420000001234567891234
RS35105008123123123173
SC74MCBL01031234567890123456USD
SK8975000000000012345671
SI56192001234567892
SO061000001123123456789
ES7921000813610123456789
SD8811123456789012
SE7280000810340009783242
CH5604835012345678009
TL380010012345678910106
TN5904018104004942712345
TR320010009999901234567890
UA903052992990004149123456789
AE460090000000123456789
GB33BUKB20201555555555
VG07ABVI0000000123456789}

  
  foreach iban $ibans {
    puts [::iban::validate $iban]
  } ;# foreach
  
  
} ;# proc test