social_security_number 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +97 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/social_security_number/country/be.rb +33 -0
- data/lib/social_security_number/country/ca.rb +48 -0
- data/lib/social_security_number/country/ch.rb +34 -0
- data/lib/social_security_number/country/cn.rb +42 -0
- data/lib/social_security_number/country/cz.rb +46 -0
- data/lib/social_security_number/country/de.rb +80 -0
- data/lib/social_security_number/country/dk.rb +48 -0
- data/lib/social_security_number/country/es.rb +41 -0
- data/lib/social_security_number/country/fi.rb +30 -0
- data/lib/social_security_number/country/fr.rb +32 -0
- data/lib/social_security_number/country/ie.rb +38 -0
- data/lib/social_security_number/country/is.rb +38 -0
- data/lib/social_security_number/country/it.rb +27 -0
- data/lib/social_security_number/country/lt.rb +57 -0
- data/lib/social_security_number/country/mx.rb +56 -0
- data/lib/social_security_number/country/nl.rb +26 -0
- data/lib/social_security_number/country/no.rb +56 -0
- data/lib/social_security_number/country/pk.rb +14 -0
- data/lib/social_security_number/country/se.rb +54 -0
- data/lib/social_security_number/country/uk.rb +68 -0
- data/lib/social_security_number/country/us.rb +52 -0
- data/lib/social_security_number/country.rb +103 -0
- data/lib/social_security_number/validator.rb +40 -0
- data/lib/social_security_number/version.rb +3 -0
- data/lib/social_security_number.rb +7 -0
- data/social_security_number.gemspec +36 -0
- metadata +126 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Fr validates France INSEE code
|
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#France
|
|
4
|
+
class Fr < Country
|
|
5
|
+
def validate
|
|
6
|
+
@error = if !check_by_regexp(REGEXP)
|
|
7
|
+
'bad number format'
|
|
8
|
+
elsif !check_control_sum
|
|
9
|
+
'number control sum invalid'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
REGEXP = /^(?<gnd>\d{1})(?<year>\d{2})(?<month>\d{2})(?<department1>\d{1})(?<department2>[0-9AB]{1})(?<place>\d{3})(?<indv>\d{3})(?<ctrl>\d{2})$/
|
|
16
|
+
|
|
17
|
+
def check_control_sum
|
|
18
|
+
count_last_number == @control_number.to_i
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def count_last_number
|
|
22
|
+
number = @civil_number[0..12]
|
|
23
|
+
department = @civil_number[5..6]
|
|
24
|
+
if department == '2A'
|
|
25
|
+
number = ("#{@civil_number[0..4]}19#{@civil_number[7..12]}")
|
|
26
|
+
elsif department == '2B'
|
|
27
|
+
number = ("#{@civil_number[0..4]}18#{@civil_number[7..12]}")
|
|
28
|
+
end
|
|
29
|
+
97 - (number.to_i % 97)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Ie validates Ireland Personal Public Service Number (PPS No)
|
|
3
|
+
# https://en.wikipedia.org/wiki/Personal_Public_Service_Number
|
|
4
|
+
# http://www.welfare.ie/en/Pages/Extension-of-the-Personal-Public-Service-Number-Range.aspx
|
|
5
|
+
class Ie < Country
|
|
6
|
+
def validate
|
|
7
|
+
@error = if !check_by_regexp(REGEXP)
|
|
8
|
+
'bad number format'
|
|
9
|
+
elsif !check_control_simbol
|
|
10
|
+
'number control sum invalid'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
MODULUS = 23
|
|
17
|
+
|
|
18
|
+
CONTROLCIPHERS = [8, 7, 6, 5, 4, 3, 2].freeze
|
|
19
|
+
|
|
20
|
+
REGEXP = /^(?<indv>\d{7})[- .]?(?<ctrl>[A-W])[AHWTX]?$/
|
|
21
|
+
|
|
22
|
+
def check_control_simbol
|
|
23
|
+
count_last_simbol.to_s == @control_number.to_s
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def count_last_simbol
|
|
27
|
+
sum = calc_sum(@individual, CONTROLCIPHERS)
|
|
28
|
+
alfabet = %w[W A B C D E F G H I J K L M N O P Q R S U V]
|
|
29
|
+
value = if @civil_number[-1] != @control_number.to_s
|
|
30
|
+
alfabet.index(@civil_number[-1]).to_i
|
|
31
|
+
else
|
|
32
|
+
0
|
|
33
|
+
end
|
|
34
|
+
last_simbol = (sum + (value * 9)) % MODULUS
|
|
35
|
+
'WABCDEFGHIJKLMNOPQRSTUV'[last_simbol]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Is validates Iceland personal and organisation identity code (Kennitala)
|
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Iceland
|
|
4
|
+
class Is < Country
|
|
5
|
+
def validate
|
|
6
|
+
@error = if !check_by_regexp(REGEXP)
|
|
7
|
+
'bad number format'
|
|
8
|
+
elsif !birth_date
|
|
9
|
+
'number birth date is invalid'
|
|
10
|
+
elsif !check_control_sum
|
|
11
|
+
'number control sum invalid'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def day
|
|
16
|
+
@day = @parsed_civil_number[:day].to_i % 40
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
MODULUS = 11
|
|
22
|
+
|
|
23
|
+
CONTROLCIPHERS = [3, 2, 7, 6, 5, 4, 3, 2].freeze
|
|
24
|
+
|
|
25
|
+
DATE_REGEXP = /(?<day>[01234567]\d)(?<month>\d{2})(?<year>\d{2})/
|
|
26
|
+
REGEXP = /^#{DATE_REGEXP}[ .-]?(?<indv>\d{2})(?<ctrl>\d{1})(?<cntr>[09])$/
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def check_control_sum
|
|
30
|
+
count_control_number == @control_number.to_i
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def count_control_number
|
|
34
|
+
sum = calc_sum(digit_number[0..9], CONTROLCIPHERS)
|
|
35
|
+
11 - sum % MODULUS
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::It validates Italy tax code for individuals (Codice fiscale)
|
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Italy
|
|
4
|
+
class It < Country
|
|
5
|
+
def validate
|
|
6
|
+
@error = if !check_by_regexp(REGEXP)
|
|
7
|
+
'bad number format'
|
|
8
|
+
elsif !birth_date
|
|
9
|
+
'number birth date is invalid'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def month
|
|
14
|
+
months = %w[A B C D E H L M P R S T]
|
|
15
|
+
@month = months.index(@parsed_civil_number[:month]).to_i + 1
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def day
|
|
19
|
+
d = @parsed_civil_number[:day].to_i
|
|
20
|
+
@day = d >= 40 ? d - 40 : d
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
DATE_REGEXP = /(?<year>[\dL-V]{2})(?<month>[ABCDEHLMPRST])(?<day>[\dL-V]{2})/
|
|
24
|
+
INVD_REGEXP = /(?<indv>[A-Z][\dL-V]{3})(?<indv2>[A-Z]{1})/
|
|
25
|
+
REGEXP = /^(?<nm>[A-Z]{6})-?#{DATE_REGEXP}#{INVD_REGEXP}$/
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Lt validates Lithuania Personal Code (Asmens kodas)
|
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Lithuania
|
|
4
|
+
class Lt < Country
|
|
5
|
+
def validate
|
|
6
|
+
@error = if !check_digits
|
|
7
|
+
'it is not number'
|
|
8
|
+
elsif !check_length(11)
|
|
9
|
+
'number should be length of 11'
|
|
10
|
+
elsif @parsed_civil_number[:gnd].to_i > 6
|
|
11
|
+
'gender number is not recognized'
|
|
12
|
+
elsif !birth_date
|
|
13
|
+
'number birth date is invalid'
|
|
14
|
+
elsif !check_control_sum
|
|
15
|
+
'number control sum invalid'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def year
|
|
20
|
+
if @parsed_civil_number
|
|
21
|
+
base = case @parsed_civil_number[:gnd].to_i
|
|
22
|
+
when 1..2 then 1800
|
|
23
|
+
when 3..4 then 1900
|
|
24
|
+
when 5..6 then 2000
|
|
25
|
+
else
|
|
26
|
+
0
|
|
27
|
+
end
|
|
28
|
+
@year = base + @parsed_civil_number[:year].to_i
|
|
29
|
+
else
|
|
30
|
+
0
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
MODULUS = 11
|
|
37
|
+
|
|
38
|
+
CONTROLCIPHERS_1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1].freeze
|
|
39
|
+
CONTROLCIPHERS_2 = [3, 4, 5, 6, 7, 8, 9, 1, 2, 3].freeze
|
|
40
|
+
|
|
41
|
+
REGEXP = /^(?<gnd>\d{1})#{SHORT_DATE_REGEXP}-?(?<indv>\d{3})(?<ctrl>\d{1})$/
|
|
42
|
+
|
|
43
|
+
def check_control_sum
|
|
44
|
+
count_last_number == @control_number.to_i
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def count_last_number
|
|
48
|
+
sum = calc_sum(@civil_number[0..9], CONTROLCIPHERS_1)
|
|
49
|
+
last_number = sum % MODULUS
|
|
50
|
+
return last_number if last_number < 10
|
|
51
|
+
sum = calc_sum(@civil_number[0..9], CONTROLCIPHERS_2)
|
|
52
|
+
last_number = sum % MODULUS
|
|
53
|
+
return last_number if last_number < 10
|
|
54
|
+
0
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Mx validates Mexico Unique Population Registry Code (Clave Única de Registro de Población (CURP))
|
|
3
|
+
# https://es.stackoverflow.com/questions/31039/c%C3%B3mo-validar-una-curp-de-m%C3%A9xico
|
|
4
|
+
# https://en.wikipedia.org/wiki/Unique_Population_Registry_Code
|
|
5
|
+
# https://es.wikipedia.org/wiki/Registro_Federal_de_Contribuyentes_(M%C3%A9xico)
|
|
6
|
+
class Mx < Country
|
|
7
|
+
def validate
|
|
8
|
+
@error = if !check_by_regexp(REGEXP)
|
|
9
|
+
'bad number format'
|
|
10
|
+
elsif !birth_date
|
|
11
|
+
'number birth date is invalid'
|
|
12
|
+
elsif !check_control_sum
|
|
13
|
+
'number control sum invalid'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def gender
|
|
18
|
+
@gender ||= @parsed_civil_number[:gnd].to_s == 'H' ? :male : :female
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
MODULUS = 10
|
|
24
|
+
|
|
25
|
+
# the first character is the initial of the first last name
|
|
26
|
+
LINIT = /(?<linit>[A-Z])/
|
|
27
|
+
# the initials of the second last name and the name
|
|
28
|
+
LINIT_2 = /(?<linit2>[A-Z]{2})/
|
|
29
|
+
# the first internal consonants of surnames and names
|
|
30
|
+
FIC = /(?<fic>[B-DF-HJ-NP-TV-Z]{3})/
|
|
31
|
+
FEDERAL = /(?<federal>(AS|BC|BS|CC|CS|CH|CL|CM|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE))/
|
|
32
|
+
SURNAME = /(?<surname>[AEIOUX])/
|
|
33
|
+
|
|
34
|
+
REGEXP = /^#{LINIT}#{SURNAME}#{LINIT_2}[ .-]?#{SHORT_DATE_REGEXP}[ .-]?(?<gnd>[HM]{1})[ .-]?#{FEDERAL}[ .-]?#{FIC}[ .-]?(?<homoclave>[A-Z\d])[ .-]?(?<ctrl>\d{1})$/
|
|
35
|
+
|
|
36
|
+
def check_control_sum
|
|
37
|
+
count_last_number.to_i == @control_number.to_i
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def count_last_number
|
|
41
|
+
n = number
|
|
42
|
+
alfabet = %W[0 1 2 3 4 5 6 7 8 9 A B C
|
|
43
|
+
D E F G H I J K L M N Ñ O P Q R S T U V W X Y Z]
|
|
44
|
+
sum = 0
|
|
45
|
+
17.times do |i|
|
|
46
|
+
sum += alfabet.index(n[i]).to_i * (18 - i)
|
|
47
|
+
end
|
|
48
|
+
last_number = 10 - sum % MODULUS
|
|
49
|
+
last_number == 10 ? 0 : last_number
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def number
|
|
53
|
+
@civil_number.gsub(' |.|-', '')
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Nl validates Netherlands Citizen's Service Number (Burgerservicenummer)
|
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Netherlands
|
|
4
|
+
class Nl < Country
|
|
5
|
+
def validate
|
|
6
|
+
@error = if !check_digits
|
|
7
|
+
'it is not number'
|
|
8
|
+
elsif !check_length(CONTROLCIPHERS.size)
|
|
9
|
+
'number should be length of 9 or 8'
|
|
10
|
+
elsif !check_control_sum
|
|
11
|
+
'number control sum invalid'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
MODULUS = 11
|
|
18
|
+
|
|
19
|
+
CONTROLCIPHERS = [9, 8, 7, 6, 5, 4, 3, 2, -1].freeze
|
|
20
|
+
|
|
21
|
+
def check_control_sum
|
|
22
|
+
sum = calc_sum(@civil_number, CONTROLCIPHERS)
|
|
23
|
+
(sum % MODULUS).zero?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::No validates Norway eleven-digit birth number (fødselsnummer)
|
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Norway
|
|
4
|
+
class No < Country
|
|
5
|
+
def validate
|
|
6
|
+
@error = if !check_by_regexp(REGEXP)
|
|
7
|
+
'bad number format'
|
|
8
|
+
elsif !birth_date
|
|
9
|
+
'number birth date is invalid'
|
|
10
|
+
elsif !check_control_digit_1
|
|
11
|
+
'first control code invalid'
|
|
12
|
+
elsif !check_control_digit_2
|
|
13
|
+
'second control code invalid'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def year
|
|
18
|
+
if @parsed_civil_number
|
|
19
|
+
year_value = (@parsed_civil_number[:indv].to_i * 10 + @parsed_civil_number[:gnd].to_i).to_i
|
|
20
|
+
base = case year_value
|
|
21
|
+
when 000..499 then 1900
|
|
22
|
+
when 500..899 then @parsed_civil_number[:year].to_i >= 54 ? 1800 : 2000
|
|
23
|
+
when 900..999 then @parsed_civil_number[:year].to_i >= 40 ? 1900 : 2000
|
|
24
|
+
else
|
|
25
|
+
0
|
|
26
|
+
end
|
|
27
|
+
@year = base + @parsed_civil_number[:year].to_i
|
|
28
|
+
else
|
|
29
|
+
0
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def day
|
|
34
|
+
@day = @parsed_civil_number[:day].to_i % 40
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
MODULUS = 11
|
|
40
|
+
|
|
41
|
+
CONTROLCIPHERS_1 = [3, 7, 6, 1, 8, 9, 4, 5, 2].freeze
|
|
42
|
+
CONTROLCIPHERS_2 = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2].freeze
|
|
43
|
+
|
|
44
|
+
REGEXP = /^#{SHORT_DATE2_REGEXP}[-+]?(?<indv>\d{2})(?<gnd>\d{1})(?<ctrl>\d{2})$/
|
|
45
|
+
|
|
46
|
+
def check_control_digit_1
|
|
47
|
+
ctrl = 11 - calc_sum(digit_number[0, 9], CONTROLCIPHERS_1) % MODULUS
|
|
48
|
+
(ctrl % MODULUS).to_s == @civil_number[-2]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def check_control_digit_2
|
|
52
|
+
ctrl = 11 - calc_sum(digit_number[0, 10], CONTROLCIPHERS_2) % MODULUS
|
|
53
|
+
(ctrl % MODULUS).to_s == @civil_number[-1]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Pk validates Pakistan computerised national identity card number (CNIC)
|
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Pakistan
|
|
4
|
+
# https://www.geo.tv/latest/157233-secret-behind-every-digit-of-the-cnic-number
|
|
5
|
+
class Pk < Country
|
|
6
|
+
def validate
|
|
7
|
+
@error = unless check_by_regexp(REGEXP)
|
|
8
|
+
'bad number format'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
REGEXP = /^(?<location>\d{5})-?(?<family_number>\d{7})-?(?<gnd>\d{1})$/
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Se validates Sweden Personal Identity Number (personnummer)
|
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Sweden
|
|
4
|
+
class Se < Country
|
|
5
|
+
def validate
|
|
6
|
+
@error = if !check_by_regexp(REGEXP)
|
|
7
|
+
'bad number format'
|
|
8
|
+
elsif !birth_date
|
|
9
|
+
'number birth date is invalid'
|
|
10
|
+
elsif !check_control_digit
|
|
11
|
+
'number control sum invalid'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def year
|
|
16
|
+
if @parsed_civil_number
|
|
17
|
+
base = case @parsed_civil_number[:year].to_i
|
|
18
|
+
when 1..40 then 2000
|
|
19
|
+
when 40..99 then 1900
|
|
20
|
+
else
|
|
21
|
+
0
|
|
22
|
+
end
|
|
23
|
+
@year = base + @parsed_civil_number[:year].to_i
|
|
24
|
+
else
|
|
25
|
+
0
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
MODULUS = 10
|
|
32
|
+
|
|
33
|
+
CONTROLCIPHERS = [2, 1, 2, 1, 2, 1, 2, 1, 2].freeze
|
|
34
|
+
|
|
35
|
+
REGEXP = /^#{DATE_REGEXP}-(?<indv>\d{2})(?<gnd>\d{1})(?<ctrl>\d{1})$/
|
|
36
|
+
|
|
37
|
+
def check_control_digit
|
|
38
|
+
sum = checksum(:even)
|
|
39
|
+
control_number = (sum % 10 != 0) ? 10 - (sum % 10) : 0
|
|
40
|
+
control_number.to_i == @control_number.to_i
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def checksum(operation)
|
|
44
|
+
i = 0
|
|
45
|
+
compare_method = operation == :even ? :== : :>
|
|
46
|
+
digit_number[0..8].reverse.split('').reduce(0) do |sum, c|
|
|
47
|
+
n = c.to_i
|
|
48
|
+
weight = (i % 2).send(compare_method, 0) ? n * 2 : n
|
|
49
|
+
i += 1
|
|
50
|
+
sum += weight < 10 ? weight : weight - 9
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Uk validates United Kingdom National Insurance number (NINO) and
|
|
3
|
+
# National Health Service number, CHI (Community Health Index) number
|
|
4
|
+
# https://en.wikipedia.org/wiki/National_identification_number#United_Kingdom
|
|
5
|
+
class Uk < Country
|
|
6
|
+
def validate
|
|
7
|
+
@error = if !validate_formats
|
|
8
|
+
'bad number format'
|
|
9
|
+
elsif check_length(10) && !(nhs_validation || chi_validation)
|
|
10
|
+
'bad CHI, NHS number'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
NINO_REGEXP = /^(?<first_letter>[A-CEGHJ-PRSTW-Z])[- ]?(?<second_letter>[A-CEGHJ-NPRSTW-Z])[- ]?(?<first_numbers>\d{2})[- ]?(?<second_numbers>\d{2})[- ]?(?<last_numbers>\d{2})[- ]?(?<last_simbols>[A-D\s])$/
|
|
17
|
+
NHS_REGEXP = /^(?<first_number>[0-9]{3})[- ]?(?<second_number>[0-9]{3})[- ]?(?<last_number>[0-9]{4})$/
|
|
18
|
+
CHI_REGEXP = /^#{SHORT_DATE2_REGEXP}[- ]?(?<last_number>[0-9]{4})$/
|
|
19
|
+
|
|
20
|
+
def validate_formats
|
|
21
|
+
check_nino_format || check_by_regexp(NHS_REGEXP) || check_by_regexp(CHI_REGEXP)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_nino_format
|
|
25
|
+
check_by_regexp(NINO_REGEXP) && !check_by_regexp(/^(GB|BG|NK|KN|TN|NT|ZZ)/)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def nhs_validation
|
|
29
|
+
check_control_sum
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def chi_validation
|
|
33
|
+
check_date
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def check_date
|
|
37
|
+
day = @civil_number[0..1]
|
|
38
|
+
month = @civil_number[2..3]
|
|
39
|
+
year = @civil_number[4..6]
|
|
40
|
+
|
|
41
|
+
Date.valid_date?(base_year(year).to_i, month.to_i, day.to_i)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def base_year(year)
|
|
45
|
+
current_year = Time.now.year % 100
|
|
46
|
+
offset_year = year.to_i
|
|
47
|
+
offset_year += 100 if year and offset_year < current_year
|
|
48
|
+
1900 + offset_year
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def check_control_sum
|
|
52
|
+
count_last_number == @civil_number[9].to_i
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def count_last_number
|
|
56
|
+
result_array = []
|
|
57
|
+
9.times do |i|
|
|
58
|
+
result_array << ((11 - (i + 1)) * @civil_number[i].to_i)
|
|
59
|
+
end
|
|
60
|
+
if (11 - (result_array.inject(:+) % 11)) == 10
|
|
61
|
+
return false
|
|
62
|
+
else
|
|
63
|
+
return 0 if result_array.inject(:+) % 11 == 0
|
|
64
|
+
return 11 - (result_array.inject(:+) % 11)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Us validates U.S. (America) Social Security number (SSN) and
|
|
3
|
+
# Individual Taxpayer Identification Number (ITIN), Employer Identification Number (EIN)
|
|
4
|
+
# https://en.wikipedia.org/wiki/National_identification_number#United_States
|
|
5
|
+
# https://www.ssa.gov/employer/verifySSN.htm
|
|
6
|
+
# https://en.wikipedia.org/wiki/Social_Security_number
|
|
7
|
+
# https://en.wikipedia.org/wiki/Individual_Taxpayer_Identification_Number
|
|
8
|
+
class Us < Country
|
|
9
|
+
def validate
|
|
10
|
+
@error = if !validate_formats
|
|
11
|
+
'bad number format'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
SSN_REGEXP = /^(?<area>\d{3})-(?<group>\d{2})-(?<invidual>\d{4})$/
|
|
18
|
+
ITIN_REGEXP = /^(?<area>\d{3})-(?<group>\d{2})-(?<invidual>\d{4})$/
|
|
19
|
+
EIN_REGEXP = /^(?<area>\d{2})-(?<group>\d{7})$/
|
|
20
|
+
|
|
21
|
+
def validate_formats
|
|
22
|
+
(check_by_regexp(SSN_REGEXP) && validate_ssn) ||
|
|
23
|
+
(check_by_regexp(ITIN_REGEXP) && validate_itin) ||
|
|
24
|
+
(check_by_regexp(EIN_REGEXP) && validate_ein)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def validate_ssn
|
|
28
|
+
matches = @civil_number.match(self.class::SSN_REGEXP) || (return nil)
|
|
29
|
+
|
|
30
|
+
if matches[:area] == '000' || matches[:area] == '666' || matches[:group] == '00' ||
|
|
31
|
+
matches[:invidual] == '0000' || matches[:area][0] == '9'
|
|
32
|
+
return false
|
|
33
|
+
else
|
|
34
|
+
return true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def validate_itin
|
|
39
|
+
matches = @civil_number.match(self.class::ITIN_REGEXP) || (return nil)
|
|
40
|
+
if [70..88].include?(matches[:group].to_i) || matches[:area][0] != '9'
|
|
41
|
+
return false
|
|
42
|
+
else
|
|
43
|
+
return true
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def validate_ein
|
|
48
|
+
matches = @civil_number.match(self.class::EIN_REGEXP) || (return nil)
|
|
49
|
+
matches[:area] =~ /^(0[1-6]||1[0-6]|2[0-7]|[35]\d|[468][0-8]|7[1-7]|9[0-58-9])$/
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Country
|
|
3
|
+
class Country
|
|
4
|
+
require 'date'
|
|
5
|
+
|
|
6
|
+
attr_accessor :civil_number, :birth_date, :individual,
|
|
7
|
+
:control_number, :error
|
|
8
|
+
|
|
9
|
+
DATE_REGEXP = /(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})/
|
|
10
|
+
SHORT_DATE2_REGEXP = /(?<day>\d{2})(?<month>\d{2})(?<year>\d{2})/
|
|
11
|
+
SHORT_DATE_REGEXP = /(?<year>\d{2})(?<month>\d{2})(?<day>\d{2})/
|
|
12
|
+
|
|
13
|
+
def initialize(civil_number)
|
|
14
|
+
@civil_number = civil_number.to_s.upcase
|
|
15
|
+
values_from_number if self.class.const_defined?('REGEXP')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def valid?
|
|
19
|
+
@error = nil
|
|
20
|
+
validate
|
|
21
|
+
@error.nil?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parsed_civil_number
|
|
25
|
+
@parsed_civil_number ||= @civil_number.match(self.class::REGEXP)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def digit_number
|
|
29
|
+
@civil_number.gsub(/[^\d]/, '')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def gender
|
|
33
|
+
return unless @parsed_civil_number.names.include?('gnd')
|
|
34
|
+
@gender ||= @parsed_civil_number[:gnd].to_i.odd? ? :male : :female
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def calc_sum(number, ciphers)
|
|
40
|
+
digits = number.split(//)
|
|
41
|
+
|
|
42
|
+
sum = 0
|
|
43
|
+
digits.each_with_index do |digit, i|
|
|
44
|
+
sum += digit.to_i * ciphers[i].to_i
|
|
45
|
+
end
|
|
46
|
+
sum
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def check_digits
|
|
50
|
+
return false unless @civil_number =~ /\A\d+\z/
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def check_by_regexp(regexp)
|
|
55
|
+
return false unless @civil_number =~ regexp
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def check_length(size)
|
|
60
|
+
return false unless @civil_number.length == size
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def year
|
|
65
|
+
return unless @parsed_civil_number.names.include?('year')
|
|
66
|
+
current_year = Time.now.year % 100
|
|
67
|
+
offset_year = @parsed_civil_number[:year].to_i
|
|
68
|
+
offset_year += 100 if offset_year && offset_year < current_year
|
|
69
|
+
@year ||= 1900 + offset_year.to_i
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def month
|
|
73
|
+
@month ||= value_from_parsed_civil_number('month').to_i
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def day
|
|
77
|
+
@day ||= value_from_parsed_civil_number('day').to_i
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def date
|
|
81
|
+
year
|
|
82
|
+
month
|
|
83
|
+
day
|
|
84
|
+
return unless @year && @month && @day &&
|
|
85
|
+
Date.valid_date?(@year, @month, @day)
|
|
86
|
+
@birth_date = Date.new(@year, @month, @day)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def value_from_parsed_civil_number(key)
|
|
90
|
+
return unless @parsed_civil_number.names.include?(key)
|
|
91
|
+
@parsed_civil_number[key.to_sym].to_s
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def values_from_number
|
|
95
|
+
parsed_civil_number
|
|
96
|
+
return unless @parsed_civil_number
|
|
97
|
+
@individual = value_from_parsed_civil_number('indv')
|
|
98
|
+
@control_number = value_from_parsed_civil_number('ctrl')
|
|
99
|
+
gender
|
|
100
|
+
date
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module SocialSecurityNumber
|
|
2
|
+
# SocialSecurityNumber::Validator
|
|
3
|
+
class Validator
|
|
4
|
+
SUPPORTED_COUNTRY_CODES = %w[BE CA CH CN DE DK ES FI FR IE
|
|
5
|
+
IS IT LT MX NL NO PK SE UK US].freeze
|
|
6
|
+
|
|
7
|
+
attr_accessor :civil_number, :country_code, :error
|
|
8
|
+
|
|
9
|
+
def initialize(params = {})
|
|
10
|
+
@civil_number = params[:number].to_s.strip.gsub(/\s+/, '').upcase
|
|
11
|
+
@country_code = params[:country_code].to_s.strip.gsub(/\s+/, '').upcase
|
|
12
|
+
|
|
13
|
+
@birth_date = params[:birth_date] ? params[:birth_date] : nil
|
|
14
|
+
@gender = params[:gender] ? params[:gender] : nil
|
|
15
|
+
|
|
16
|
+
unless self.class::SUPPORTED_COUNTRY_CODES.include?(@country_code)
|
|
17
|
+
raise "Unexpected country code '#{country_code}' that is not yet supported"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def valid?
|
|
22
|
+
civil_number = SocialSecurityNumber.const_get(@country_code.capitalize).new(@civil_number)
|
|
23
|
+
|
|
24
|
+
if civil_number.valid?
|
|
25
|
+
if !@birth_date.nil? && !civil_number.birth_date.nil? && civil_number.birth_date.to_s != @birth_date.to_s
|
|
26
|
+
@error = "birth date #{@birth_date} dont match #{civil_number.birth_date}"
|
|
27
|
+
return false
|
|
28
|
+
end
|
|
29
|
+
if !@gender.nil? && !civil_number.gender.nil? && civil_number.gender.to_s.strip != @gender.to_s.strip
|
|
30
|
+
@error = "gender #{@gender} dont match #{civil_number.gender}"
|
|
31
|
+
return false
|
|
32
|
+
end
|
|
33
|
+
return true
|
|
34
|
+
end
|
|
35
|
+
@error = civil_number.error
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
require 'social_security_number/version'
|
|
2
|
+
|
|
3
|
+
module SocialSecurityNumber
|
|
4
|
+
autoload :Validator, 'social_security_number/validator'
|
|
5
|
+
autoload :Country, 'social_security_number/country'
|
|
6
|
+
Dir["#{__dir__}/social_security_number/country/*.rb"].each { |f| require f }
|
|
7
|
+
end
|