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.
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