IBAN: Difference between revisions

From Rosetta Code
Content added Content deleted
m (Fixed tags)
Line 112: Line 112:
=={{header|Caché ObjectScript}}==
=={{header|Caché ObjectScript}}==


<lang cache>Class Utils.Validate [ Abstract ]
<lang cos>Class Utils.Validate [ Abstract ]
{
{



Revision as of 19:25, 6 April 2013

IBAN is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
This page uses content from Wikipedia. The original article was at IBAN. The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance)

The International Bank Account Number (IBAN) is an internationally agreed means of identifying bank accounts across national borders with a reduced risk of propagating transcription errors. The IBAN consists of up to 34 alphanumeric characters: first the two-letter ISO 3166-1 alpha-2 country code, then two check digits, and finally a country-specific Basic Bank Account Number (BBAN). The check digits enable a sanity check of the bank account number to confirm its integrity even before submitting a transaction.

The task here is to validate the following fictitious IBAN: GB82 WEST 1234 5698 7654 32. Details of the algorithm can be found on the Wikipedia page.

C

<lang C>#include <alloca.h>

  1. include <ctype.h>
  2. include <stdio.h>
  3. include <stdlib.h>
  4. include <string.h>
  1. define V(cc, exp) if (!strncmp(iban, cc, 2)) return len == exp

/* Validate country code against expected length. */ int valid_cc(const char *iban, int len) {

   V("AL", 28); V("AD", 24); V("AT", 20); V("AZ", 28); V("BE", 16); V("BH", 22); V("BA", 20); V("BR", 29);
   V("BG", 22); V("CR", 21); V("HR", 21); V("CY", 28); V("CZ", 24); V("DK", 18); V("DO", 28); V("EE", 20);
   V("FO", 18); V("FI", 18); V("FR", 27); V("GE", 22); V("DE", 22); V("GI", 23); V("GR", 27); V("GL", 18);
   V("GT", 28); V("HU", 28); V("IS", 26); V("IE", 22); V("IL", 23); V("IT", 27); V("KZ", 20); V("KW", 30);
   V("LV", 21); V("LB", 28); V("LI", 21); V("LT", 20); V("LU", 20); V("MK", 19); V("MT", 31); V("MR", 27);
   V("MU", 30); V("MC", 27); V("MD", 24); V("ME", 22); V("NL", 18); V("NO", 15); V("PK", 24); V("PS", 29);
   V("PL", 28); V("PT", 25); V("RO", 24); V("SM", 27); V("SA", 24); V("RS", 22); V("SK", 24); V("SI", 19);
   V("ES", 24); V("SE", 24); V("CH", 21); V("TN", 24); V("TR", 26); V("AE", 23); V("GB", 22); V("VG", 24);
   return 0;

}

/* Remove blanks from s in-place, return its new length. */ int strip(char *s) {

   int i = -1, m = 0;
   while(s[++i]) {
       s[i - m] = s[i];
       m += s[i] <= 32;
   }
   s[i - m] = 0;
   return i - m;

}

/* Calculate the mod 97 of an arbitrarily large number (as a string). */ int mod97(const char *s, int len) {

   int i, j, parts = len / 7;
   char rem[10] = "00";
   for (i = 1; i <= parts + (len % 7 != 0); ++i) {
       strncpy(rem + 2, s + (i - 1) * 7, 7);
       j = atoi(rem) % 97;
       rem[0] = j / 10 + '0';
       rem[1] = j % 10 + '0';
   }
   return atoi(rem) % 97;

}

int valid_iban(char *iban) {

   int i, j, l = 0, sz = strip(iban);
   char *rot, *trans;
   /* Ensure upper alphanumeric input and count letters. */
   for (i = 0; i < sz; ++i) {
       if (!isdigit(iban[i]) && !isupper(iban[i]))
           return 0;
       l += !!isupper(iban[i]);
   }
   if (!valid_cc(iban, sz))
       return 0;
   /* Move the first four characters to the end. */
   rot = alloca(sz);
   strcpy(rot, iban + 4);
   strncpy(rot + sz - 4, iban, 4);
   /* Allocate space for the transformed IBAN. */
   trans = alloca(sz + l);
   trans[sz + l] = 0;
   /* Convert A to 10, B to 11, etc. */
   for (i = j = 0; i < sz; ++i, ++j) {
       if (isdigit(rot[i]))
           trans[j] = rot[i];
       else {
           trans[j]   = (rot[i] - 55) / 10 + '0';
           trans[++j] = (rot[i] - 55) % 10 + '0';
       }
   }
   return mod97(trans, sz + l) == 1;

}

int main(int _, char **argv) {

   while (--_, *++argv)
       printf("%s is %svalid.\n", *argv, valid_iban(*argv) ? "" : "in");
   return 0;

}</lang>

Output:
iban 'GB82 WEST 1234 5698 7654 32' GB82TEST12345698765432
GB82WEST12345698765432 is valid.
GB82TEST12345698765432 is invalid.

Caché ObjectScript

<lang cos>Class Utils.Validate [ Abstract ] {

ClassMethod VerifyIBAN(pIBAN As %String = "") As %Boolean { // remove spaces and define parts Set iban=$Translate(pIBAN, " ") Set cc=$Extract(iban, 1, 2) Set cd=$Extract(iban, 3, 4) Set bban=$Extract(iban, 5, *)

// ensure IBAN is correct format If $Match(iban, ..GetIBANPattern(cc))=0 Quit 0

// compare result and return Quit cd=..GetIBANCheckDigit(cc, bban) }

ClassMethod GetIBANCheckDigit(pCC As %String, pBBAN As %String) As %Integer [ Internal, Private ] { Set str=pBBAN_pCC_"00" For i=1:1 { Set chr=$Extract(str, i) If chr="" Quit If chr?1U Set $Extract(str, i)=$ASCII(chr)-55 } Set cd=98-..GetModulus(str, 97) Quit $Select($Length(cd)=2: cd, 1: "0"_cd) }

ClassMethod GetModulus(pNum As %Integer, pDiv As %Integer) As %Integer [ Internal, Private ] { While $Length(pNum)>9 { Set $Extract(pNum, 1, 9)=$Extract(pNum, 1, 9)#pDiv } Quit pNum#pDiv }

ClassMethod GetIBANPattern(pCC As %String = "") As %String [ Internal, Private ] { Quit $Case(pCC, "AL": "^AL\d{10}[0-9A-Z]{16}$", "AD": "^AD\d{10}[0-9A-Z]{12}$", "AT": "^AT\d{18}$", "BH": "^BH\d{2}[A-Z]{4}[0-9A-Z]{14}$", "BE": "^BE\d{14}$", "BA": "^BA\d{18}$", "BG": "^BG\d{2}[A-Z]{4}\d{6}[0-9A-Z]{8}$", "HR": "^HR\d{19}$", "CY": "^CY\d{10}[0-9A-Z]{16}$", "CZ": "^CZ\d{22}$", "DK": "^DK\d{16}$|^FO\d{16}$|^GL\d{16}$", "DO": "^DO\d{2}[0-9A-Z]{4}\d{20}$", "EE": "^EE\d{18}$", "FI": "^FI\d{16}$", "FR": "^FR\d{12}[0-9A-Z]{11}\d{2}$", "GE": "^GE\d{2}[A-Z]{2}\d{16}$", "DE": "^DE\d{20}$", "GI": "^GI\d{2}[A-Z]{4}[0-9A-Z]{15}$", "GR": "^GR\d{9}[0-9A-Z]{16}$", "HU": "^HU\d{26}$", "IS": "^IS\d{24}$", "IE": "^IE\d{2}[A-Z]{4}\d{14}$", "IL": "^IL\d{21}$", "IT": "^IT\d{2}[A-Z]\d{10}[0-9A-Z]{12}$", "KZ": "^[A-Z]{2}\d{5}[0-9A-Z]{13}$", "KW": "^KW\d{2}[A-Z]{4}22!$", "LV": "^LV\d{2}[A-Z]{4}[0-9A-Z]{13}$", "LB": "^LB\d{6}[0-9A-Z]{20}$", "LI": "^LI\d{7}[0-9A-Z]{12}$", "LT": "^LT\d{18}$", "LU": "^LU\d{5}[0-9A-Z]{13}$", "MK": "^MK\d{5}[0-9A-Z]{10}\d{2}$", "MT": "^MT\d{2}[A-Z]{4}\d{5}[0-9A-Z]{18}$", "MR": "^MR13\d{23}$", "MU": "^MU\d{2}[A-Z]{4}\d{19}[A-Z]{3}$", "MC": "^MC\d{12}[0-9A-Z]{11}\d{2}$", "ME": "^ME\d{20}$", "NL": "^NL\d{2}[A-Z]{4}\d{10}$", "NO": "^NO\d{13}$", "PL": "^PL\d{10}[0-9A-Z]{,16}n$", "PT": "^PT\d{23}$", "RO": "^RO\d{2}[A-Z]{4}[0-9A-Z]{16}$", "SM": "^SM\d{2}[A-Z]\d{10}[0-9A-Z]{12}$", "SA": "^SA\d{4}[0-9A-Z]{18}$", "RS": "^RS\d{20}$", "SK": "^SK\d{22}$", "SI": "^SI\d{17}$", "ES": "^ES\d{22}$", "SE": "^SE\d{22}$", "CH": "^CH\d{7}[0-9A-Z]{12}$", "TN": "^TN59\d{20}$", "TR": "^TR\d{7}[0-9A-Z]{17}$", "AE": "^AE\d{21}$", "GB": "^GB\d{2}[A-Z]{4}\d{14}$", : " ") }

}</lang>

Examples:
USER>For  { Read iban Quit:iban=""  Write " => ", ##class(Utils.Validate).VerifyIBAN(iban), ! }
GB82 WEST 1234 5698 7654 32 => 1
GB82 TEST 1234 5698 7654 32 => 0
GR16 0110 1250 0000 0001 2300 695 => 1
GB29 NWBK 6016 1331 9268 19 => 1
SA03 8000 0000 6080 1016 7519 => 1
CH93 0076 2011 6238 5295 7 => 1
IL62 0108 0000 0009 9999 999 => 1

USER>

Clojure

<lang Clojure>(def explen

 {"AL" 28 "AD" 24 "AT" 20 "AZ" 28 "BE" 16 "BH" 22 "BA" 20 "BR" 29
  "BG" 22 "CR" 21 "HR" 21 "CY" 28 "CZ" 24 "DK" 18 "DO" 28 "EE" 20
  "FO" 18 "FI" 18 "FR" 27 "GE" 22 "DE" 22 "GI" 23 "GR" 27 "GL" 18
  "GT" 28 "HU" 28 "IS" 26 "IE" 22 "IL" 23 "IT" 27 "KZ" 20 "KW" 30
  "LV" 21 "LB" 28 "LI" 21 "LT" 20 "LU" 20 "MK" 19 "MT" 31 "MR" 27
  "MU" 30 "MC" 27 "MD" 24 "ME" 22 "NL" 18 "NO" 15 "PK" 24 "PS" 29
  "PL" 28 "PT" 25 "RO" 24 "SM" 27 "SA" 24 "RS" 22 "SK" 24 "SI" 19
  "ES" 24 "SE" 24 "CH" 21 "TN" 24 "TR" 26 "AE" 23 "GB" 22 "VG" 24})

(defn valid-iban? [iban]

 (let [iban (apply str (remove #{\space \tab} iban))]
   (cond
     ; Ensure upper alphanumeric input.
     (not (re-find #"^[\dA-Z]+$" iban)) false
     ; Validate country code against expected length.
     (not= (explen (subs iban 0 2)) (count iban)) false
     :else
     (let [rot   (flatten (apply conj (split-at 4 iban)))
           trans (map #(read-string (str "36r" %)) rot)]
       (= 1 (mod (bigint (apply str trans)) 97))))))

(prn (valid-iban? "GB82 WEST 1234 5698 7654 32")  ; true

    (valid-iban? "GB82 TEST 1234 5698 7654 32")) ; false</lang>

D

Translation of: Python

<lang d>import std.stdio, std.string, std.regex, std.conv, std.bigint,

      std.algorithm, std.ascii;

immutable int[string] country2len; static this() {

   country2len = ["AL":28, "AD":24, "AT":20, "AZ":28, "BE":16,
   "BH":22, "BA":20, "BR":29, "BG":22, "CR":21, "HR":21, "CY":28,
   "CZ":24, "DK":18, "DO":28, "EE":20, "FO":18, "FI":18, "FR":27,
   "GE":22, "DE":22, "GI":23, "GR":27, "GL":18, "GT":28, "HU":28,
   "IS":26, "IE":22, "IL":23, "IT":27, "KZ":20, "KW":30, "LV":21,
   "LB":28, "LI":21, "LT":20, "LU":20, "MK":19, "MT":31, "MR":27,
   "MU":30, "MC":27, "MD":24, "ME":22, "NL":18, "NO":15, "PK":24,
   "PS":29, "PL":28, "PT":25, "RO":24, "SM":27, "SA":24, "RS":22,
   "SK":24, "SI":19, "ES":24, "SE":24, "CH":21, "TN":24, "TR":26,
   "AE":23, "GB":22, "VG":24];

}

bool validIBAN(string iban) {

   // Ensure upper alphanumeric input.
   iban = iban.removechars(whitespace);
   if (!iban.match(r"^[\dA-Z]+$"))
       return false;
   // Validate country code against expected length.
   if (iban.length != country2len[iban[0 .. 2]])
       return false;
   // Shift and convert. BASE 36: 0..9,A..Z -> 0..35.
   iban = iban[4 .. $] ~ iban[0 .. 4];
   return iban.map!(c => [c].to!int(36).text).join.BigInt % 97 == 1;

}

void main() {

   foreach (account; ["GB82 WEST 1234 5698 7654 32",
                      "GB82 TEST 1234 5698 7654 32"])
       writefln("%s validation is: %s", account, account.validIBAN);

}</lang>

Output:
GB82 WEST 1234 5698 7654 32 validation is: true
GB82 TEST 1234 5698 7654 32 validation is: false

Perl 6

<lang perl6>for (

   'GB82 WEST 1234 5698 7654 32',
   'gb82 west 1234 5698 7654 32',
   'GB82 TEST 1234 5698 7654 32'
   ) {
   printf "%s is %svalid.\n", $_, is_valid_iban($_) ??  !! 'NOT '

}


sub is_valid_iban (Str $iban is copy) {

   my %len = <
       AD 24 AE 23 AL 28 AT 20 AZ 28 BA 20 BE 16 BG 22 BH 22 BR 29 CH 21
       CR 21 CY 28 CZ 24 DE 22 DK 18 DO 28 EE 20 ES 24 FI 18 FO 18 FR 27
       GB 22 GE 22 GI 23 GL 18 GR 27 GT 28 HR 21 HU 28 IE 22 IL 23 IS 26
       IT 27 KW 30 KZ 20 LB 28 LI 21 LT 20 LU 20 LV 21 MC 27 MD 24 ME 22
       MK 19 MR 27 MT 31 MU 30 NL 18 NO 15 PK 24 PL 28 PS 29 PT 25 RO 24
       RS 22 SA 24 SE 24 SI 19 SK 24 SM 27 TN 24 TR 26 VG 24
   >;
   $iban ~~ s:g/\s//;
   return 0 if $iban ~~ m/<-[0..9A..Za..z]>/;
   my $country = $iban.uc.substr(0,2);
   return 0 unless %len.exists($country) and $iban.chars == %len{$country};
   $iban ~~ s/(.**4)(.+)/$1$0/;
   $iban.subst(:g, /(\D)/, -> $/ {:36("$0")}) % 97 == 1;

}</lang>

GB82 WEST 1234 5698 7654 32 is valid.
gb82 west 1234 5698 7654 32 is valid.
GB82 TEST 1234 5698 7654 32 is NOT valid.

Python

Translation of: Ruby

<lang python>import re

_country2length = dict(

   AL=28, AD=24, AT=20, AZ=28, BE=16, BH=22, BA=20, BR=29,
   BG=22, CR=21, HR=21, CY=28, CZ=24, DK=18, DO=28, EE=20,
   FO=18, FI=18, FR=27, GE=22, DE=22, GI=23, GR=27, GL=18,
   GT=28, HU=28, IS=26, IE=22, IL=23, IT=27, KZ=20, KW=30,
   LV=21, LB=28, LI=21, LT=20, LU=20, MK=19, MT=31, MR=27,
   MU=30, MC=27, MD=24, ME=22, NL=18, NO=15, PK=24, PS=29,
   PL=28, PT=25, RO=24, SM=27, SA=24, RS=22, SK=24, SI=19,
   ES=24, SE=24, CH=21, TN=24, TR=26, AE=23, GB=22, VG=24 )

def valid_iban(iban):

   # Ensure upper alphanumeric input.
   iban = iban.replace(' ',).replace('\t',)
   if not re.match(r'^[\dA-Z]+$', iban): 
       return False
   # Validate country code against expected length.
   if len(iban) != _country2length[iban[:2]]:
       return False
   # Shift and convert.
   iban = iban[4:] + iban[:4]
   digits = int(.join(str(int(ch, 36)) for ch in iban)) #BASE 36: 0..9,A..Z -> 0..35
   return digits % 97 == 1

if __name__ == '__main__':

   for account in ["GB82 WEST 1234 5698 7654 32", "GB82 TEST 1234 5698 7654 32"]:
       print('%s validation is: %s' % (account, valid_iban(account)))</lang>
Output:
GB82 WEST 1234 5698 7654 32 validation is: True
GB82 TEST 1234 5698 7654 32 validation is: False

Ruby

Works with: Ruby version 1.9+

<lang Ruby>def valid_iban? iban

 len = {
   AL: 28, AD: 24, AT: 20, AZ: 28, BE: 16, BH: 22, BA: 20, BR: 29,
   BG: 22, CR: 21, HR: 21, CY: 28, CZ: 24, DK: 18, DO: 28, EE: 20,
   FO: 18, FI: 18, FR: 27, GE: 22, DE: 22, GI: 23, GR: 27, GL: 18,
   GT: 28, HU: 28, IS: 26, IE: 22, IL: 23, IT: 27, KZ: 20, KW: 30,
   LV: 21, LB: 28, LI: 21, LT: 20, LU: 20, MK: 19, MT: 31, MR: 27,
   MU: 30, MC: 27, MD: 24, ME: 22, NL: 18, NO: 15, PK: 24, PS: 29,
   PL: 28, PT: 25, RO: 24, SM: 27, SA: 24, RS: 22, SK: 24, SI: 19,
   ES: 24, SE: 24, CH: 21, TN: 24, TR: 26, AE: 23, GB: 22, VG: 24
 }
 # Ensure upper alphanumeric input.
 iban.delete! " \t"
 return false unless iban =~ /^[\dA-Z]+$/
 # Validate country code against expected length.
 cc = iban[0, 2].to_sym
 return false unless iban.size == len[cc]
 # Shift and convert.
 iban = iban[4..-1] + iban[0, 4]
 iban.gsub!(/./) { |c| c.to_i(36) }
 iban.to_i % 97 == 1

end

p valid_iban? "GB82 WEST 1234 5698 7654 32" #=> true p valid_iban? "GB82 TEST 1234 5698 7654 32" #=> false</lang>

Tcl

<lang tcl>proc verifyIBAN {iban} {

   # Normalize by up-casing and stripping illegal chars (e.g., space)
   set iban [regsub -all {[^A-Z0-9]+} [string toupper $iban] ""]
   # Get the expected length from the country-code part
   switch [string range $iban 0 1] {

NO { set len 15 } BE { set len 16 } DK - FI - FO - GL - NL { set len 18} MK - SI { set len 19 } AT - BA - EE - KZ - LT - LU { set len 20 } CH - CR - HR - LI - LV { set len 21 } BG - BH - DE - GB - GE - IE - ME - RS { set len 22 } AE - GI - IL { set len 23 } AD - CZ - ES - MD - PK - RO - SA - SE - SK - TN - VG { set len 24 } PT { set len 25 } IS - TR { set len 26 } FR - GR - IT - MC - MR - SM { set len 27 } AL - AZ - CY - DO - GT - HU - LB - PL { set len 28 } BR - PS { set len 29 } KW - MU { set len 30 } MT { set len 31 } default { # unsupported country code return false }

   }
   # Convert to number
   set num [string map {

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

   } [string range $iban 4 end][string range $iban 0 3]]
   # Verify length and modulus
   return [expr {[string length $iban] == $len && $num % 97 == 1}]

}</lang> Demonstrating: <lang tcl>set iban "GB82 WEST 1234 5698 7654 32" puts "$iban is [expr {[verifyIBAN $iban] ? {verified} : {unverified}}]" set not "GB42 WEST 1234 5698 7654 32" puts "$not is [expr {[verifyIBAN $not] ? {verified} : {unverified}}]"</lang>

Output:
GB82 WEST 1234 5698 7654 32 is verified
GB42 WEST 1234 5698 7654 32 is unverified