Skip to content

Instantly share code, notes, and snippets.

@datenimperator
Created December 3, 2013 11:57
Show Gist options
  • Save datenimperator/7767986 to your computer and use it in GitHub Desktop.
Save datenimperator/7767986 to your computer and use it in GitHub Desktop.
An International Securities Identification Number (ISIN) uniquely identifies a security. Its structure is defined in ISO 6166. This Ruby code implements a validity check for a given ISIN string.
class Isin
COUNTRY_CODES = %w{AF AX AL DZ AS AD AO AI AQ AG AR AM AW AU AT AZ BS BH BD BB BY BE BZ BJ BM BT BO BQ BA BW BV BR IO BN BG BF BI KH CM CA CV KY CF TD CL CN CX CC CO KM CG CD CK CR CI HR CU CW CY CZ DK DJ DM DO EC EG SV GQ ER EE ET FK FO FJ FI FR GF PF TF GA GM GE DE GH GI GR GL GD GP GU GT GG GN GW GY HT HM VA HN HK HU IS IN ID IR IQ IE IM IL IT JM JP JE JO KZ KE KI KP KR KW KG LA LV LB LS LR LY LI LT LU MO MK MG MW MY MV ML MT MH MQ MR MU YT MX FM MD MC MN ME MS MA MZ MM NA NR NP NL NC NZ NI NE NG NU NF MP NO OM PK PW PS PA PG PY PE PH PN PL PT PR QA RE RO RU RW BL SH KN LC MF PM VC WS SM ST SA SN RS SC SL SG SX SK SI SB SO ZA GS SS ES LK SD SR SJ SZ SE CH SY TW TJ TZ TH TL TG TK TO TT TN TR TM TC TV UG UA AE GB US UM UY UZ VU VE VN VG VI WF EH YE ZM ZW}
def initialize(v)
@content = v.upcase
end
def to_s
@content
end
def valid?
return false if @content.blank?
if m = @content.match(/^(\w{2})(\w{9})(\d)$/)
country, identifier, checksum = m.captures
return false unless COUNTRY_CODES.include?(country)
if c = self.class.calculate_checksum(country + identifier)
return true if checksum.to_i == c
end
end
false
end
def self.calculate_checksum(txt)
return nil unless txt.ascii_only?
digits = txt.chars.map do |c|
case c
when 'A'..'Z' then (c.ord - 55).to_s.chars #we're ascii-only, A has code 65
when '0'..'9' then c
end
end.flatten.map(&:to_i)
pos = 1
sum = digits.reverse.map{|a| pos += 1; (a*(2-pos%2)).to_s}.join.chars.map(&:to_i).sum
(10 - (sum%10)) % 10
end
end
require 'spec_helper'
describe Isin do
it "can be instantiated" do
i = Isin.new 'DE000BAY0017'
i.to_s.should == 'DE000BAY0017'
end
context "validity" do
it "validates proper ISIN" do
%w{DE000BAY0017 AU0000XVGZA3 US0378331005 US30231G1022}.each do |isin|
Isin.new(isin).valid?.should be_true
end
end
it "detects invalid formats" do
%w{ZZ1234567890 DE00BAY0017 DE000BAY00178}.each do |isin|
Isin.new(isin).valid?.should be_false
end
end
it "detects invalid checksum" do
Isin.new('DE000BAY0018').valid?.should be_false
Isin.new('DE000BAZ0017').valid?.should be_false
Isin.new('AU0000XVGZA5').valid?.should be_false
end
it "accepts lowercase" do
Isin.new('de000bay0017').valid?.should be_true
end
end
end
@jake-007
Copy link

jake-007 commented Jul 9, 2024

Eleven years later and we have 'EU' as a valid country code :)

@datenimperator
Copy link
Author

@jake-007 eleven years later, and I don't even remember what I used ISINs for back in the day :-)

Seriously, if you have any use for it, please feel free to edit the code as you may see fit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment