Created
December 3, 2013 11:57
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 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
Eleven years later and we have 'EU' as a valid country code :)