├── .gitignore ├── Rakefile ├── iban-tools.gemspec ├── lib ├── iban-tools │ ├── iban_rules.rb │ ├── iban.rb │ └── rules.yml └── iban-tools.rb ├── LICENSE ├── README.md └── spec └── iban-tools └── iban_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # vim:ts=2:sw=2:et: 2 | 3 | require 'rubygems' 4 | gem 'rspec', '>= 1.2.4' 5 | require 'spec/rake/spectask' 6 | 7 | Spec::Rake::SpecTask.new do |t| 8 | t.libs << 'lib' 9 | t.spec_opts = ["--color" ] 10 | end 11 | 12 | -------------------------------------------------------------------------------- /iban-tools.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.platform = Gem::Platform::RUBY 3 | s.summary = "IBAN validator" 4 | s.name = 'iban-tools' 5 | s.version = '0.0.7' 6 | s.authors = ["Iulian Dogariu"] 7 | s.email = ["code@iuliandogariu.com"] 8 | s.requirements << 'none' 9 | s.require_path = 'lib' 10 | s.files = [ 11 | "README.md", 12 | "lib/iban-tools.rb", 13 | "lib/iban-tools/iban.rb", 14 | "lib/iban-tools/iban_rules.rb", 15 | "lib/iban-tools/rules.yml" 16 | ] 17 | s.description = "Validates IBAN account numbers" 18 | end 19 | -------------------------------------------------------------------------------- /lib/iban-tools/iban_rules.rb: -------------------------------------------------------------------------------- 1 | # vim:ts=2:sw=2:et: 2 | 3 | require 'yaml' 4 | 5 | module IBANTools 6 | 7 | class IBANRules 8 | 9 | def initialize( rules = {} ) 10 | @rules = rules 11 | end 12 | 13 | def [](key) 14 | @rules[key] 15 | end 16 | 17 | def self.defaults 18 | load_from_string( File.read(File.dirname(__FILE__) + "/rules.yml") ) 19 | end 20 | 21 | def self.load_from_string( string ) 22 | rule_hash = YAML.load(string) 23 | rule_hash.each do |country_code, specs| 24 | specs["bban_pattern"] = Regexp.new("^" + specs["bban_pattern"] + "$") 25 | end 26 | IBANRules.new(rule_hash) 27 | end 28 | 29 | end 30 | 31 | end 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Iulian Dogariu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /lib/iban-tools.rb: -------------------------------------------------------------------------------- 1 | # vim:ts=2:sw=2:et: 2 | #-- 3 | # Copyright (c) 2009 Iulian Dogariu 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining 6 | # a copy of this software and associated documentation files (the 7 | # "Software"), to deal in the Software without restriction, including 8 | # without limitation the rights to use, copy, modify, merge, publish, 9 | # distribute, sublicense, and/or sell copies of the Software, and to 10 | # permit persons to whom the Software is furnished to do so, subject to 11 | # the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | #++ 24 | 25 | require 'iban-tools/iban.rb' 26 | require 'iban-tools/iban_rules.rb' 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iban-tools 2 | 3 | iban-tools is a Ruby library for manipulating and validating IBAN account numbers. You can [read more about IBAN](http://en.wikipedia.org/wiki/International_Bank_Account_Number) on Wikipedia 4 | 5 | ## Credit 6 | 7 | [Iulianu](http://github.com/iulianu) wrote [iban-tools](http://github.com/iulianu/iban-tools). We just removed use of `String#ord` for compatibility with Ruby 1.8 and 1.9 and of course pushed the built gem to gemcutter. 8 | 9 | You'll find the [source code](http://github.com/iulianu/iban-tools) on Github. [Our fork](http://github.com/alphasights/iban-tools) is also available on GitHub 10 | 11 | The gem should be compatible with Ruby 1.8.6, 1.8.7 and 1.9.1. 12 | 13 | ## INSTALLATION 14 | 15 | You'll need to add http://gems.rubyforge.com as a gem source if you don't already have it. 16 | 17 | You can accomplish this with the gemcutter gem. 18 | 19 | gem install gemcutter 20 | gem tumble 21 | 22 | Once you have the gemcutter source added you can install iban-tools with a simple gem install. 23 | 24 | gem install iban-tools 25 | 26 | ## USAGE 27 | 28 | require 'rubygems' 29 | require 'iban-tools' 30 | 31 | IBANTools::IBAN.valid?("GB82 WEST 1234 5698 7654 32") 32 | => true 33 | 34 | Advanced usage, gives more detailed error messages 35 | 36 | IBANTools::IBAN.new("XQ75 BADCODE 666").validation_errors 37 | => [:unknown_country_code, :bad_check_digits] 38 | 39 | Pretty print, canonicalize, and extract fields from an IBAN code 40 | 41 | iban = IBANTools::IBAN.new(" ro49 aaaa 1B31007593840000") 42 | 43 | iban.code 44 | => "RO49AAAA1B31007593840000" 45 | 46 | iban.country_code 47 | => "RO" 48 | 49 | iban.prettify 50 | => "RO49 AAAA 1B31 0075 9384 0000" 51 | -------------------------------------------------------------------------------- /lib/iban-tools/iban.rb: -------------------------------------------------------------------------------- 1 | # vim:ts=2:sw=2:et: 2 | 3 | module IBANTools 4 | class IBAN 5 | 6 | def self.valid?( code, rules = nil ) 7 | new(code).validation_errors(rules).empty? 8 | end 9 | 10 | def initialize( code ) 11 | @code = IBAN.canonicalize_code(code) 12 | end 13 | 14 | def validation_errors( rules = nil ) 15 | errors = [] 16 | return [:too_short] if @code.size < 5 17 | return [:bad_chars] unless @code =~ /^[A-Z0-9]+$/ 18 | errors += validation_errors_against_rules( rules || IBAN.default_rules ) 19 | errors << :bad_check_digits unless valid_check_digits? 20 | errors 21 | end 22 | 23 | def validation_errors_against_rules( rules ) 24 | errors = [] 25 | return [:unknown_country_code] if rules[country_code].nil? 26 | errors << :bad_length if rules[country_code]["length"] != @code.size 27 | errors << :bad_format unless bban =~ rules[country_code]["bban_pattern"] 28 | errors 29 | end 30 | 31 | # The code in canonical form, 32 | # suitable for storing in a database 33 | # or sending over the wire 34 | def code 35 | @code 36 | end 37 | 38 | def country_code 39 | @code[0..1] 40 | end 41 | 42 | def check_digits 43 | @code[2..3] 44 | end 45 | 46 | def bban 47 | @code[4..-1] 48 | end 49 | 50 | def valid_check_digits? 51 | numerify.to_i % 97 == 1 52 | end 53 | 54 | def numerify 55 | if bad_match = @code.match(/[^A-Z0-9]/) 56 | raise RuntimeError.new("Unexpected byte '#{bad_match[0].bytes.first}' in IBAN code '#{prettify}'") 57 | end 58 | (@code[4..-1] + @code[0..3]).gsub(/[A-Z]/) { |let| (let.ord - 55).to_s } 59 | end 60 | 61 | def to_s 62 | "#<#{self.class}: #{prettify}>" 63 | end 64 | 65 | # The IBAN code in a human-readable format 66 | def prettify 67 | @code.gsub(/(.{4})/, '\1 ').strip 68 | end 69 | 70 | def self.canonicalize_code( code ) 71 | code.strip.gsub(/\s+/, '').upcase 72 | end 73 | 74 | # Load and cache the default rules from rules.yml 75 | def self.default_rules 76 | @default_rules ||= IBANRules.defaults 77 | end 78 | 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/iban-tools/iban_spec.rb: -------------------------------------------------------------------------------- 1 | # vim:ts=2:sw=2:et: 2 | 3 | require 'iban-tools' 4 | 5 | module IBANTools 6 | describe IBAN do 7 | 8 | describe "with test rules" do 9 | 10 | before(:each) do 11 | @rules = IBANRules.new({ "GB" => {"length" => 22, "bban_pattern" => /[A-Z]{4}.*/} }) 12 | end 13 | 14 | it "should validate IBAN code" do 15 | # Using example from http://en.wikipedia.org/wiki/IBAN#Calculating_and_validating_IBAN_checksums 16 | IBAN.valid?( "GB82WEST12345698765432", @rules ).should be_true 17 | end 18 | 19 | it "should reject IBAN code with invalid characters" do 20 | IBAN.new("gb99 %BC").validation_errors(@rules). 21 | should include(:bad_chars) 22 | end 23 | 24 | it "should reject IBAN code from unknown country" do 25 | # Norway is not present in @rules 26 | IBAN.new("NO9386011117947").validation_errors(@rules). 27 | should == [:unknown_country_code] 28 | end 29 | 30 | it "should reject IBAN code that does not match the length for the respective country" do 31 | IBAN.new("GB88 WEST 1234 5698 7654 3").validation_errors(@rules). 32 | should == [:bad_length] 33 | # Length is 21, should be 22. 34 | # check digits are good though 35 | end 36 | 37 | it "should reject IBAN code that does not match the pattern for the selected country" do 38 | IBAN.new("GB69 7654 1234 5698 7654 32").validation_errors(@rules). 39 | should == [:bad_format] 40 | # Length and check digits are good, 41 | # but country pattern calls for chars 4-7 to be letters. 42 | end 43 | 44 | it "should reject IBAN code with invalid check digits" do 45 | IBAN.valid?( "GB99 WEST 1234 5698 7654 32", @rules ).should be_false 46 | 47 | IBAN.new("GB99 WEST 1234 5698 7654 32").validation_errors(@rules). 48 | should == [:bad_check_digits] 49 | end 50 | end 51 | 52 | it "should numerify IBAN code" do 53 | IBAN.new("GB82 WEST 1234 5698 7654 32").numerify. 54 | should == "3214282912345698765432161182" 55 | end 56 | 57 | it "should canonicalize IBAN code" do 58 | IBAN.new(" gb82 WeSt 1234 5698 7654 32").code. 59 | should == "GB82WEST12345698765432" 60 | end 61 | 62 | it "should pretty-print IBAN code" do 63 | IBAN.new(" GB82W EST12 34 5698 765432 ").prettify. 64 | should == "GB82 WEST 1234 5698 7654 32" 65 | 66 | IBAN.new(" GB82W EST12 34 5698 765432 ").to_s. 67 | should == "#" 68 | end 69 | 70 | it "should extract ISO country code" do 71 | IBAN.new("NO9386011117947").country_code.should == "NO" 72 | end 73 | 74 | it "should extract check digits" do 75 | IBAN.new("NO6686011117947").check_digits.should == "66" 76 | # extract check digits even if they are invalid! 77 | end 78 | 79 | it "should extract BBAN (Basic Bank Account Number)" do 80 | IBAN.new("NO9386011117947").bban.should == "86011117947" 81 | end 82 | 83 | describe "with default rules" do 84 | 85 | # Rules are loaded from lib/iban-tools/rules.yml 86 | # Samples from http://www.tbg5-finance.org/?ibandocs.shtml/ 87 | 88 | [ "AD1200012030200359100100", 89 | "AE070331234567890123456", 90 | "AL47212110090000000235698741", 91 | "AT611904300234573201", 92 | "AZ21NABZ00000000137010001944", 93 | "BA391290079401028494", 94 | "BE68539007547034", 95 | "BG80BNBG96611020345678", 96 | "BH67BMAG00001299123456", 97 | "BR7724891749412660603618210F3", 98 | "CH9300762011623852957", 99 | "CR0515202001026284066", 100 | "CY17002001280000001200527600", 101 | "CZ6508000000192000145399", 102 | "DE89370400440532013000", 103 | "DK5000400440116243", 104 | "DO28BAGR00000001212453611324", 105 | "EE382200221020145685", 106 | "ES9121000418450200051332", 107 | "FI2112345600000785", 108 | "FO7630004440960235", 109 | "FR1420041010050500013M02606", 110 | "GB29NWBK60161331926819", 111 | "GE29NB0000000101904917", 112 | "GI75NWBK000000007099453", 113 | "GL4330003330229543", 114 | "GR1601101250000000012300695", 115 | "GT82TRAJ01020000001210029690", 116 | "HR1210010051863000160", 117 | "HU42117730161111101800000000", 118 | "IE29AIBK93115212345678", 119 | "IL620108000000099999999", 120 | "IS140159260076545510730339", 121 | "IT60X0542811101000000123456", 122 | "KW81CBKU0000000000001234560101", 123 | "KZ86125KZT5004100100", 124 | "LB62099900000001001901229114", 125 | "LI21088100002324013AA", 126 | "LT121000011101001000", 127 | "LU280019400644750000", 128 | "LV80BANK0000435195001", 129 | "MC1112739000700011111000h79", 130 | "MD24AG000225100013104168", 131 | "ME25505000012345678951", 132 | "MK07300000000042425", 133 | "MR1300020001010000123456753", 134 | "MT84MALT011000012345MTLCAST001S", 135 | "MU17BOMM0101101030300200000MUR", 136 | "NL91ABNA0417164300", 137 | "NO9386011117947", 138 | "PK36SCBL0000001123456702", 139 | "PL27114020040000300201355387", 140 | "PS92PALS000000000400123456702", 141 | "PT50000201231234567890154", 142 | "QA58DOHB00001234567890ABCDEFG", 143 | "RO49AAAA1B31007593840000", 144 | "RS35260005601001611379", 145 | "SA0380000000608010167519", 146 | "SE3550000000054910000003", 147 | "SI56191000000123438", 148 | "SK3112000000198742637541", 149 | "SM86U0322509800000000270100", 150 | "TL380080012345678910157", 151 | "TN5914207207100707129648", 152 | "TR330006100519786457841326", 153 | "VG96VPVG0000012345678901", 154 | "XK051212012345678906" 155 | ].each do |iban_code| 156 | describe iban_code do 157 | it "should be valid" do 158 | IBAN.new(iban_code).validation_errors.should == [] 159 | end 160 | end 161 | end 162 | 163 | it "should fail known pattern violations" do 164 | # This IBAN has valid check digits 165 | # but should fail because of pattern violation 166 | IBAN.valid?("RO7999991B31007593840000").should be_false 167 | end 168 | 169 | end 170 | 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /lib/iban-tools/rules.yml: -------------------------------------------------------------------------------- 1 | # Data from http://www.tbg5-finance.org/?ibandocs.shtml/ 2 | 3 | 'AD': 4 | # Andorra 5 | length: 24 6 | bban_pattern: '\d{8}[A-Z0-9]{12}' 7 | 8 | 'AE': 9 | # United Arab Emirates 10 | length: 23 11 | bban_pattern: '\d{19}' 12 | 13 | 'AL': 14 | # Albania 15 | length: 28 16 | bban_pattern: '\d{8}[A-Z0-9]{16}' 17 | 18 | 'AT': 19 | # Austria 20 | length: 20 21 | bban_pattern: '\d{16}' 22 | 23 | 'AZ': 24 | # Azerbaijan, Republic of 25 | length: 28 26 | bban_pattern: '\d[0-9]{2}[A-Z]{4}[A-Z0-9]{20}' 27 | 28 | 'BA': 29 | # Bosnia 30 | length: 20 31 | bban_pattern: '\d{16}' 32 | 33 | 'BE': 34 | # Belgium 35 | length: 16 36 | bban_pattern: '\d{12}' 37 | 38 | 'BG': 39 | # Bulgaria 40 | length: 22 41 | bban_pattern: '[A-Z]{4}\d{6}[A-Z0-9]{8}' 42 | 43 | 'BH': 44 | # Bahrain 45 | length: 22 46 | bban_pattern: '[A-Z]{4}[A-Z0-9]{14}' 47 | 48 | 'BR': 49 | # Brazil 50 | length: 29 51 | bban_pattern: '\d[0-9]{2}[0-9]{8}[0-9]{5}[0-9]{10}[A-Z]{1}[A-Z0-9]{1}' 52 | 53 | 'CH': 54 | # Switzerland 55 | length: 21 56 | bban_pattern: '\d{5}[A-Z0-9]{12}' 57 | 58 | 'CR': 59 | # Costa Rica 60 | length: 21 61 | bban_pattern: '\d[0-9]{2}[0-9]{3}[0-9]{14}' 62 | 63 | 'CY': 64 | # Cyprus 65 | length: 28 66 | bban_pattern: '\d{8}[A-Z0-9]{16}' 67 | 68 | 'CZ': 69 | # Czech Republic 70 | length: 24 71 | bban_pattern: '\d{20}' 72 | 73 | 'DE': 74 | # Germany 75 | length: 22 76 | bban_pattern: '\d{18}' 77 | 78 | 'DK': 79 | # Denmark 80 | length: 18 81 | bban_pattern: '\d{14}' 82 | 83 | 'DO': 84 | # Dominican Republic 85 | length: 28 86 | bban_pattern: '[A-Z]{4}\d{20}' 87 | 88 | 'EE': 89 | # Estonia 90 | length: 20 91 | bban_pattern: '\d{16}' 92 | 93 | 'ES': 94 | # Spain 95 | length: 24 96 | bban_pattern: '\d{20}' 97 | 98 | 'FI': 99 | # Finland 100 | length: 18 101 | bban_pattern: '\d{14}' 102 | 103 | 'FO': 104 | # Faroe Islands 105 | length: 18 106 | bban_pattern: '\d{14}' 107 | 108 | 'FR': 109 | # France 110 | length: 27 111 | bban_pattern: '\d{10}[A-Z0-9]{11}\d{2}' 112 | 113 | 'GB': 114 | # United Kingdom 115 | length: 22 116 | bban_pattern: '[A-Z]{4}\d{14}' 117 | 118 | 'GE': 119 | # Georgia 120 | length: 22 121 | bban_pattern: '[A-Z]{2}\d{16}' 122 | 123 | 'GI': 124 | # Gibraltar 125 | length: 23 126 | bban_pattern: '[A-Z]{4}[A-Z0-9]{15}' 127 | 128 | 'GL': 129 | # Greenland 130 | length: 18 131 | bban_pattern: '\d{14}' 132 | 133 | 'GR': 134 | # Greece 135 | length: 27 136 | bban_pattern: '\d{7}[A-Z0-9]{16}' 137 | 138 | 'GT': 139 | # Guatemala 140 | length: 28 141 | bban_pattern: '\d[0-9]{2}[A-Z0-9]{4}[A-Z0-9]{20}' 142 | 143 | 'HR': 144 | # Croatia 145 | length: 21 146 | bban_pattern: '\d{17}' 147 | 148 | 'HU': 149 | # Hungary 150 | length: 28 151 | bban_pattern: '\d{24}' 152 | 153 | 'IE': 154 | # Ireland 155 | length: 22 156 | bban_pattern: '[A-Z]{4}\d{14}' 157 | 158 | 'IL': 159 | # Israel 160 | length: 23 161 | bban_pattern: '\d{19}' 162 | 163 | 'IS': 164 | # Iceland 165 | length: 26 166 | bban_pattern: '\d{22}' 167 | 168 | 'IT': 169 | # Italy 170 | length: 27 171 | bban_pattern: '[A-Z]\d{10}[A-Z0-9]{12}' 172 | 173 | 'KW': 174 | # Kuwait 175 | length: 30 176 | bban_pattern: '[A-Z]{4}\d{22}' 177 | 178 | 'KZ': 179 | # Kazakhstan 180 | length: 20 181 | bban_pattern: '[0-9]{3}[A-Z0-9]{13}' 182 | 183 | 'LB': 184 | # Lebanon 185 | length: 28 186 | bban_pattern: '\d{4}[A-Z0-9]{20}' 187 | 188 | 'LI': 189 | # Liechtenstein 190 | length: 21 191 | bban_pattern: '\d{5}[A-Z0-9]{12}' 192 | 193 | 'LT': 194 | # Lithuania 195 | length: 20 196 | bban_pattern: '\d{16}' 197 | 198 | 'LU': 199 | # Luxembourg 200 | length: 20 201 | bban_pattern: '\d{3}[A-Z0-9]{13}' 202 | 203 | 'LV': 204 | # Latvia 205 | length: 21 206 | bban_pattern: '[A-Z]{4}[A-Z0-9]{13}' 207 | 208 | 'MC': 209 | # Monaco 210 | length: 27 211 | bban_pattern: '\d{10}[A-Z0-9]{11}\d{2}' 212 | 213 | 'MD': 214 | # Moldova 215 | length: 24 216 | bban_pattern: '\d[0-9]{2}[A-Z0-9]{20}' 217 | 218 | 'ME': 219 | # Montenegro 220 | length: 22 221 | bban_pattern: '\d{18}' 222 | 223 | 'MK': 224 | # Macedonia 225 | length: 19 226 | bban_pattern: '\d{3}[A-Z0-9]{10}\d{2}' 227 | 228 | 'MR': 229 | # Mauritania 230 | length: 27 231 | bban_pattern: '\d{23}' 232 | 233 | 'MT': 234 | # Malta 235 | length: 31 236 | bban_pattern: '[A-Z]{4}\d{5}[A-Z0-9]{18}' 237 | 238 | 'MU': 239 | # Mauritius 240 | length: 30 241 | bban_pattern: '[A-Z]{4}\d{19}[A-Z]{3}' 242 | 243 | 'NL': 244 | # Netherlands 245 | length: 18 246 | bban_pattern: '[A-Z]{4}\d{10}' 247 | 248 | 'NO': 249 | # Norway 250 | length: 15 251 | bban_pattern: '\d{11}' 252 | 253 | 'PK': 254 | # Pakistan 255 | length: 24 256 | bban_pattern: '\d[0-9]{2}[A-Z]{4}[A-Z0-9]{16}' 257 | 258 | 'PL': 259 | # Poland 260 | length: 28 261 | bban_pattern: '\d{8}[A-Z0-9]{16}' 262 | 263 | 'PS': 264 | # Palestinian Territory, Occupied 265 | length: 29 266 | bban_pattern: '\d[0-9]{2}[A-Z]{4}[A-Z0-9]{21}' 267 | 268 | 'PT': 269 | # Portugal 270 | length: 25 271 | bban_pattern: '\d{21}' 272 | 273 | 'QA': 274 | # Qatar 275 | length: 29 276 | bban_pattern: '\d[0-9]{2}[A-Z]{4}[A-Z0-9]{21}' 277 | 278 | 'RO': 279 | # Romania 280 | length: 24 281 | bban_pattern: '[A-Z]{4}[A-Z0-9]{16}' 282 | 283 | 'RS': 284 | # Serbia 285 | length: 22 286 | bban_pattern: '\d{18}' 287 | 288 | 'SA': 289 | # Saudi Arabia 290 | length: 24 291 | bban_pattern: '\d{2}[A-Z0-9]{18}' 292 | 293 | 'SE': 294 | # Sweden 295 | length: 24 296 | bban_pattern: '\d{20}' 297 | 298 | 'SI': 299 | # Slovenia 300 | length: 19 301 | bban_pattern: '\d{15}' 302 | 303 | 'SK': 304 | # Slovakia 305 | length: 24 306 | bban_pattern: '\d{20}' 307 | 308 | 'SM': 309 | # San Marino 310 | length: 27 311 | bban_pattern: '[A-Z]\d{10}[A-Z0-9]{12}' 312 | 313 | 'TL': 314 | # Timor-Leste 315 | length: 23 316 | bban_pattern: '\d[0-9]{2}[0-9]{3}[0-9]{14}[0-9]{2}' 317 | 318 | 'TN': 319 | # Tunisia 320 | length: 24 321 | bban_pattern: '\d{20}' 322 | 323 | 'TR': 324 | # Turkey 325 | length: 26 326 | bban_pattern: '\d{5}[A-Z0-9]{17}' 327 | 328 | 'UA': 329 | # Ukraine 330 | length: 29 331 | bban_pattern: '\d{25}' 332 | 333 | 'VG': 334 | # Virgin Islands, British 335 | length: 24 336 | bban_pattern: '\d[0-9]{2}[A-Z]{4}[0-9]{16}' 337 | 338 | 'XK': 339 | # Kosovo, Republic of 340 | length: 20 341 | bban_pattern: '\d[0-9]{2}[0-9]{4}[A-Z0-9]{12}' 342 | --------------------------------------------------------------------------------