social_security_number 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +6 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +97 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/lib/social_security_number/country/be.rb +33 -0
  13. data/lib/social_security_number/country/ca.rb +48 -0
  14. data/lib/social_security_number/country/ch.rb +34 -0
  15. data/lib/social_security_number/country/cn.rb +42 -0
  16. data/lib/social_security_number/country/cz.rb +46 -0
  17. data/lib/social_security_number/country/de.rb +80 -0
  18. data/lib/social_security_number/country/dk.rb +48 -0
  19. data/lib/social_security_number/country/es.rb +41 -0
  20. data/lib/social_security_number/country/fi.rb +30 -0
  21. data/lib/social_security_number/country/fr.rb +32 -0
  22. data/lib/social_security_number/country/ie.rb +38 -0
  23. data/lib/social_security_number/country/is.rb +38 -0
  24. data/lib/social_security_number/country/it.rb +27 -0
  25. data/lib/social_security_number/country/lt.rb +57 -0
  26. data/lib/social_security_number/country/mx.rb +56 -0
  27. data/lib/social_security_number/country/nl.rb +26 -0
  28. data/lib/social_security_number/country/no.rb +56 -0
  29. data/lib/social_security_number/country/pk.rb +14 -0
  30. data/lib/social_security_number/country/se.rb +54 -0
  31. data/lib/social_security_number/country/uk.rb +68 -0
  32. data/lib/social_security_number/country/us.rb +52 -0
  33. data/lib/social_security_number/country.rb +103 -0
  34. data/lib/social_security_number/validator.rb +40 -0
  35. data/lib/social_security_number/version.rb +3 -0
  36. data/lib/social_security_number.rb +7 -0
  37. data/social_security_number.gemspec +36 -0
  38. 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,3 @@
1
+ module SocialSecurityNumber
2
+ VERSION = '0.1.1'
3
+ 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